You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

564 lines
16 KiB

  1. /*
  2. * This file is part of PowerDNS or dnsdist.
  3. * Copyright -- PowerDNS.COM B.V. and its contributors
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of version 2 of the GNU General Public License as
  7. * published by the Free Software Foundation.
  8. *
  9. * In addition, for the avoidance of any doubt, permission is granted to
  10. * link this program with OpenSSL and to (re)distribute the binaries
  11. * produced as the result of such linking.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21. */
  22. #ifdef HAVE_CONFIG_H
  23. #include "config.h"
  24. #endif
  25. #include "ascii.hh"
  26. #include "dnsparser.hh"
  27. #include "sstuff.hh"
  28. #include "misc.hh"
  29. #include "dnswriter.hh"
  30. #include "dnsrecords.hh"
  31. #include "misc.hh"
  32. #include <fstream>
  33. #include "dns.hh"
  34. #include "zoneparser-tng.hh"
  35. #include <deque>
  36. #include <boost/algorithm/string.hpp>
  37. #include <system_error>
  38. static string g_INstr("IN");
  39. ZoneParserTNG::ZoneParserTNG(const string& fname, const DNSName& zname, const string& reldir) : d_reldir(reldir),
  40. d_zonename(zname), d_defaultttl(3600),
  41. d_templatecounter(0), d_templatestop(0),
  42. d_templatestep(0), d_havedollarttl(false){
  43. stackFile(fname);
  44. }
  45. ZoneParserTNG::ZoneParserTNG(const vector<string> zonedata, const DNSName& zname):
  46. d_zonename(zname), d_zonedata(zonedata), d_defaultttl(3600),
  47. d_templatecounter(0), d_templatestop(0), d_templatestep(0),
  48. d_havedollarttl(false), d_fromfile(false)
  49. {
  50. d_zonedataline = d_zonedata.begin();
  51. }
  52. void ZoneParserTNG::stackFile(const std::string& fname)
  53. {
  54. FILE *fp=fopen(fname.c_str(), "r");
  55. if(!fp) {
  56. std::error_code ec (errno,std::generic_category());
  57. throw std::system_error(ec, "Unable to open file '"+fname+"': "+stringerror());
  58. }
  59. filestate fs(fp, fname);
  60. d_filestates.push(fs);
  61. d_fromfile = true;
  62. }
  63. ZoneParserTNG::~ZoneParserTNG()
  64. {
  65. while(!d_filestates.empty()) {
  66. fclose(d_filestates.top().d_fp);
  67. d_filestates.pop();
  68. }
  69. }
  70. static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
  71. {
  72. return string(line.c_str() + range.first, range.second - range.first);
  73. }
  74. static bool isTimeSpec(const string& nextpart)
  75. {
  76. if(nextpart.empty())
  77. return false;
  78. for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
  79. if(isdigit(*iter))
  80. continue;
  81. if(iter+1 != nextpart.end())
  82. return false;
  83. char c=tolower(*iter);
  84. return (c=='s' || c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
  85. }
  86. return true;
  87. }
  88. unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
  89. {
  90. if(str.empty())
  91. return 0;
  92. unsigned int val;
  93. try {
  94. val=pdns_stou(str);
  95. }
  96. catch (const std::out_of_range& oor) {
  97. throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
  98. }
  99. char lc=dns_tolower(str[str.length()-1]);
  100. if(!isdigit(lc))
  101. switch(lc) {
  102. case 's':
  103. break;
  104. case 'm':
  105. val*=60; // minutes, not months!
  106. break;
  107. case 'h':
  108. val*=3600;
  109. break;
  110. case 'd':
  111. val*=3600*24;
  112. break;
  113. case 'w':
  114. val*=3600*24*7;
  115. break;
  116. case 'y': // ? :-)
  117. val*=3600*24*365;
  118. break;
  119. default:
  120. throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
  121. }
  122. return val;
  123. }
  124. bool ZoneParserTNG::getTemplateLine()
  125. {
  126. if(d_templateparts.empty() || d_templatecounter > d_templatestop) // no template, or done with
  127. return false;
  128. string retline;
  129. for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
  130. if(iter != d_templateparts.begin())
  131. retline+=" ";
  132. string part=makeString(d_templateline, *iter);
  133. /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
  134. and radix being 'd', 'o', 'x' or 'X', defaulting to 'd'.
  135. The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
  136. output will be '010', from the input of ${15,3,x}
  137. */
  138. string outpart;
  139. outpart.reserve(part.size()+5);
  140. bool inescape=false;
  141. for(string::size_type pos = 0; pos < part.size() ; ++pos) {
  142. char c=part[pos];
  143. if(inescape) {
  144. outpart.append(1, c);
  145. inescape=false;
  146. continue;
  147. }
  148. if(part[pos]=='\\') {
  149. inescape=true;
  150. continue;
  151. }
  152. if(c=='$') {
  153. if(pos + 1 == part.size() || part[pos+1]!='{') { // a trailing $, or not followed by {
  154. outpart.append(std::to_string(d_templatecounter));
  155. continue;
  156. }
  157. // need to deal with { case
  158. pos+=2;
  159. string::size_type startPos=pos;
  160. for(; pos < part.size() && part[pos]!='}' ; ++pos)
  161. ;
  162. if(pos == part.size()) // partial spec
  163. break;
  164. // we are on the '}'
  165. string spec(part.c_str() + startPos, part.c_str() + pos);
  166. int offset=0, width=0;
  167. char radix='d';
  168. sscanf(spec.c_str(), "%d,%d,%c", &offset, &width, &radix); // parse format specifier
  169. char tmp[80];
  170. switch (radix) {
  171. case 'o':
  172. snprintf(tmp, sizeof(tmp), "%0*o", width, d_templatecounter + offset);
  173. break;
  174. case 'x':
  175. snprintf(tmp, sizeof(tmp), "%0*x", width, d_templatecounter + offset);
  176. break;
  177. case 'X':
  178. snprintf(tmp, sizeof(tmp), "%0*X", width, d_templatecounter + offset);
  179. break;
  180. case 'd':
  181. default:
  182. snprintf(tmp, sizeof(tmp), "%0*d", width, d_templatecounter + offset);
  183. break;
  184. }
  185. outpart+=tmp;
  186. }
  187. else
  188. outpart.append(1, c);
  189. }
  190. retline+=outpart;
  191. }
  192. d_templatecounter+=d_templatestep;
  193. d_line = retline;
  194. return true;
  195. }
  196. static void chopComment(string& line)
  197. {
  198. if(line.find(';')==string::npos)
  199. return;
  200. string::size_type pos, len = line.length();
  201. bool inQuote=false;
  202. for(pos = 0 ; pos < len; ++pos) {
  203. if(line[pos]=='\\')
  204. pos++;
  205. else if(line[pos]=='"')
  206. inQuote=!inQuote;
  207. else if(line[pos]==';' && !inQuote)
  208. break;
  209. }
  210. if(pos != len)
  211. line.resize(pos);
  212. }
  213. static bool findAndElide(string& line, char c)
  214. {
  215. string::size_type pos, len = line.length();
  216. bool inQuote=false;
  217. for(pos = 0 ; pos < len; ++pos) {
  218. if(line[pos]=='\\')
  219. pos++;
  220. else if(line[pos]=='"')
  221. inQuote=!inQuote;
  222. else if(line[pos]==c && !inQuote)
  223. break;
  224. }
  225. if(pos != len) {
  226. line.erase(pos, 1);
  227. return true;
  228. }
  229. return false;
  230. }
  231. DNSName ZoneParserTNG::getZoneName()
  232. {
  233. return d_zonename;
  234. }
  235. string ZoneParserTNG::getLineOfFile()
  236. {
  237. if (d_zonedata.size() > 0)
  238. return "on line "+std::to_string(std::distance(d_zonedata.begin(), d_zonedataline))+" of given string";
  239. if (d_filestates.empty())
  240. return "";
  241. return "on line "+std::to_string(d_filestates.top().d_lineno)+" of file '"+d_filestates.top().d_filename+"'";
  242. }
  243. pair<string,int> ZoneParserTNG::getLineNumAndFile()
  244. {
  245. if (d_filestates.empty())
  246. return {"", 0};
  247. else
  248. return {d_filestates.top().d_filename, d_filestates.top().d_lineno};
  249. }
  250. bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
  251. {
  252. retry:;
  253. if(!getTemplateLine() && !getLine())
  254. return false;
  255. boost::trim_right_if(d_line, is_any_of(" \t\r\n\x1a"));
  256. if(comment)
  257. comment->clear();
  258. if(comment && d_line.find(';') != string::npos)
  259. *comment = d_line.substr(d_line.find(';'));
  260. d_parts.clear();
  261. vstringtok(d_parts, d_line);
  262. if(d_parts.empty())
  263. goto retry;
  264. if(d_parts[0].first != d_parts[0].second && d_line[d_parts[0].first]==';') // line consisting of nothing but comments
  265. goto retry;
  266. if(d_line[0]=='$') {
  267. string command=makeString(d_line, d_parts[0]);
  268. if(pdns_iequals(command,"$TTL") && d_parts.size() > 1) {
  269. d_defaultttl=makeTTLFromZone(trim_right_copy_if(makeString(d_line, d_parts[1]), is_any_of(";")));
  270. d_havedollarttl=true;
  271. }
  272. else if(pdns_iequals(command,"$INCLUDE") && d_parts.size() > 1 && d_fromfile) {
  273. string fname=unquotify(makeString(d_line, d_parts[1]));
  274. if(!fname.empty() && fname[0]!='/' && !d_reldir.empty())
  275. fname=d_reldir+"/"+fname;
  276. stackFile(fname);
  277. }
  278. else if(pdns_iequals(command, "$ORIGIN") && d_parts.size() > 1) {
  279. d_zonename = DNSName(makeString(d_line, d_parts[1]));
  280. }
  281. else if(pdns_iequals(command, "$GENERATE") && d_parts.size() > 2) {
  282. if (!d_generateEnabled) {
  283. throw exception("$GENERATE is not allowed in this zone");
  284. }
  285. // $GENERATE 1-127 $ CNAME $.0
  286. string range=makeString(d_line, d_parts[1]);
  287. d_templatestep=1;
  288. d_templatestop=0;
  289. sscanf(range.c_str(),"%u-%u/%u", &d_templatecounter, &d_templatestop, &d_templatestep);
  290. if (d_templatestep < 1 ||
  291. d_templatestop < d_templatecounter) {
  292. throw exception("Invalid $GENERATE parameters");
  293. }
  294. if (d_maxGenerateSteps != 0) {
  295. size_t numberOfSteps = (d_templatestop - d_templatecounter) / d_templatestep;
  296. if (numberOfSteps > d_maxGenerateSteps) {
  297. throw exception("The number of $GENERATE steps (" + std::to_string(numberOfSteps) + ") is too high, the maximum is set to " + std::to_string(d_maxGenerateSteps));
  298. }
  299. }
  300. d_templateline=d_line;
  301. d_parts.pop_front();
  302. d_parts.pop_front();
  303. d_templateparts=d_parts;
  304. goto retry;
  305. }
  306. else
  307. throw exception("Can't parse zone line '"+d_line+"' "+getLineOfFile());
  308. goto retry;
  309. }
  310. bool prevqname=false;
  311. string qname = makeString(d_line, d_parts[0]); // Don't use DNSName here!
  312. if(dns_isspace(d_line[0])) {
  313. rr.qname=d_prevqname;
  314. prevqname=true;
  315. }else {
  316. rr.qname=DNSName(qname);
  317. d_parts.pop_front();
  318. if(qname.empty() || qname[0]==';')
  319. goto retry;
  320. }
  321. if(qname=="@")
  322. rr.qname=d_zonename;
  323. else if(!prevqname && !isCanonical(qname))
  324. rr.qname += d_zonename;
  325. d_prevqname=rr.qname;
  326. if(d_parts.empty())
  327. throw exception("Line with too little parts "+getLineOfFile());
  328. string nextpart;
  329. rr.ttl=d_defaultttl;
  330. bool haveTTL=0, haveQTYPE=0;
  331. pair<string::size_type, string::size_type> range;
  332. while(!d_parts.empty()) {
  333. range=d_parts.front();
  334. d_parts.pop_front();
  335. nextpart=makeString(d_line, range);
  336. if(nextpart.empty())
  337. break;
  338. if(nextpart.find(';')!=string::npos) {
  339. break;
  340. }
  341. // cout<<"Next part: '"<<nextpart<<"'"<<endl;
  342. if(pdns_iequals(nextpart, g_INstr)) {
  343. // cout<<"Ignoring 'IN'\n";
  344. continue;
  345. }
  346. if(!haveTTL && !haveQTYPE && isTimeSpec(nextpart)) {
  347. rr.ttl=makeTTLFromZone(nextpart);
  348. if(!d_havedollarttl)
  349. d_defaultttl = rr.ttl;
  350. haveTTL=true;
  351. // cout<<"ttl is probably: "<<rr.ttl<<endl;
  352. continue;
  353. }
  354. if(haveQTYPE)
  355. break;
  356. try {
  357. rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
  358. // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
  359. haveQTYPE=1;
  360. continue;
  361. }
  362. catch(...) {
  363. throw runtime_error("Parsing zone content "+getLineOfFile()+
  364. ": '"+nextpart+
  365. "' doesn't look like a qtype, stopping loop");
  366. }
  367. }
  368. if(!haveQTYPE)
  369. throw exception("Malformed line "+getLineOfFile()+": '"+d_line+"'");
  370. // rr.content=d_line.substr(range.first);
  371. rr.content.assign(d_line, range.first, string::npos);
  372. chopComment(rr.content);
  373. trim_if(rr.content, is_any_of(" \r\n\t\x1a"));
  374. if(rr.content.size()==1 && rr.content[0]=='@')
  375. rr.content=d_zonename.toString();
  376. if(findAndElide(rr.content, '(')) { // have found a ( and elided it
  377. if(!findAndElide(rr.content, ')')) {
  378. while(getLine()) {
  379. trim_right(d_line);
  380. chopComment(d_line);
  381. trim(d_line);
  382. bool ended = findAndElide(d_line, ')');
  383. rr.content+=" "+d_line;
  384. if(ended)
  385. break;
  386. }
  387. }
  388. }
  389. trim_if(rr.content, is_any_of(" \r\n\t\x1a"));
  390. vector<string> recparts;
  391. switch(rr.qtype.getCode()) {
  392. case QType::MX:
  393. stringtok(recparts, rr.content);
  394. if(recparts.size()==2) {
  395. if (recparts[1]!=".") {
  396. try {
  397. recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
  398. } catch (std::exception &e) {
  399. throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
  400. }
  401. }
  402. rr.content=recparts[0]+" "+recparts[1];
  403. }
  404. break;
  405. case QType::RP:
  406. stringtok(recparts, rr.content);
  407. if(recparts.size()==2) {
  408. recparts[0] = toCanonic(d_zonename, recparts[0]).toStringRootDot();
  409. recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
  410. rr.content=recparts[0]+" "+recparts[1];
  411. }
  412. break;
  413. case QType::SRV:
  414. stringtok(recparts, rr.content);
  415. if(recparts.size()==4) {
  416. if(recparts[3]!=".") {
  417. try {
  418. recparts[3] = toCanonic(d_zonename, recparts[3]).toStringRootDot();
  419. } catch (std::exception &e) {
  420. throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
  421. }
  422. }
  423. rr.content=recparts[0]+" "+recparts[1]+" "+recparts[2]+" "+recparts[3];
  424. }
  425. break;
  426. case QType::NS:
  427. case QType::CNAME:
  428. case QType::DNAME:
  429. case QType::PTR:
  430. try {
  431. rr.content = toCanonic(d_zonename, rr.content).toStringRootDot();
  432. } catch (std::exception &e) {
  433. throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
  434. }
  435. break;
  436. case QType::AFSDB:
  437. stringtok(recparts, rr.content);
  438. if(recparts.size() == 2) {
  439. try {
  440. recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
  441. } catch (std::exception &e) {
  442. throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
  443. }
  444. } else {
  445. throw PDNSException("AFSDB record for "+rr.qname.toLogString()+" invalid");
  446. }
  447. rr.content.clear();
  448. for(string::size_type n = 0; n < recparts.size(); ++n) {
  449. if(n)
  450. rr.content.append(1,' ');
  451. rr.content+=recparts[n];
  452. }
  453. break;
  454. case QType::SOA:
  455. stringtok(recparts, rr.content);
  456. if(recparts.size() > 7)
  457. throw PDNSException("SOA record contents for "+rr.qname.toLogString()+" contains too many parts");
  458. if(recparts.size() > 1) {
  459. try {
  460. recparts[0]=toCanonic(d_zonename, recparts[0]).toStringRootDot();
  461. recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
  462. } catch (std::exception &e) {
  463. throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
  464. }
  465. }
  466. rr.content.clear();
  467. for(string::size_type n = 0; n < recparts.size(); ++n) {
  468. if(n)
  469. rr.content.append(1,' ');
  470. if(n > 1)
  471. rr.content+=std::to_string(makeTTLFromZone(recparts[n]));
  472. else
  473. rr.content+=recparts[n];
  474. }
  475. break;
  476. default:;
  477. }
  478. return true;
  479. }
  480. bool ZoneParserTNG::getLine()
  481. {
  482. if (d_zonedata.size() > 0) {
  483. if (d_zonedataline != d_zonedata.end()) {
  484. d_line = *d_zonedataline;
  485. ++d_zonedataline;
  486. return true;
  487. }
  488. return false;
  489. }
  490. while(!d_filestates.empty()) {
  491. if(stringfgets(d_filestates.top().d_fp, d_line)) {
  492. d_filestates.top().d_lineno++;
  493. return true;
  494. }
  495. fclose(d_filestates.top().d_fp);
  496. d_filestates.pop();
  497. }
  498. return false;
  499. }