root/trunk/pdns/pdns/zoneparser-tng.cc @ 952

Revision 952, 8.5 KB (checked in by ahu, 6 years ago)

teach zoneparser-tng about $ORIGIN and $GENERATE, is now feature complete to take over zoneparser in main pdns auth server

Line 
1/*
2    PowerDNS Versatile Database Driven Nameserver
3    Copyright (C) 2005 - 2007  PowerDNS.COM BV
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License version 2
7    as published by the Free Software Foundation
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17*/
18
19#include "dnsparser.hh"
20#include "sstuff.hh"
21#include "misc.hh"
22#include "dnswriter.hh"
23#include "dnsrecords.hh"
24#include "misc.hh"
25#include <fstream>
26#include "dns.hh"
27#include "zoneparser-tng.hh"
28#include <deque>
29#include <boost/algorithm/string.hpp>
30#include <boost/lexical_cast.hpp>
31
32ZoneParserTNG::ZoneParserTNG(const string& fname, const string& zname) : d_zonename(zname), d_defaultttl(3600)
33{
34  stackFile(fname);
35}
36
37void ZoneParserTNG::stackFile(const std::string& fname)
38{
39  FILE *fp=fopen(fname.c_str(), "r");
40  if(!fp)
41    throw runtime_error("Unable to open file '"+fname+"': "+stringerror());
42  d_fps.push(fp);
43}
44
45ZoneParserTNG::~ZoneParserTNG()
46{
47  while(!d_fps.empty()) {
48    fclose(d_fps.top());
49    d_fps.pop();
50  }
51}
52
53static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
54{
55  return string(line.c_str() + range.first, range.second - range.first);
56}
57
58static unsigned int makeTTLFromZone(const string& str)
59{
60  if(str.empty())
61    return 0;
62
63  unsigned int val=atoi(str.c_str());
64  char lc=toupper(str[str.length()-1]);
65  if(!isdigit(lc))
66    switch(lc) {
67    case 'H':
68      val*=3600;
69      break;
70    case 'D':
71      val*=3600*24;
72      break;
73    case 'W':
74      val*=3600*24*7;
75      break;
76    case 'M':
77      val*=3600*24*7*4;
78      break;
79    case 'Y': // ? :-)
80      val*=3600*24*365;
81      break;
82    default:
83      throw ZoneParserTNG::exception("Unable to parse time specification '"+str+"'");
84    }
85  return val;
86}
87
88bool ZoneParserTNG::getTemplateLine()
89{
90  if(d_templatecounter > d_templatestop || d_templateparts.empty()) // no template, or done with
91    return false;
92
93  string retline;
94  for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
95    if(iter != d_templateparts.begin())
96      retline+=" ";
97
98    string part=makeString(d_templateline, *iter);
99   
100    /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
101       and radix beging 'd', 'o', 'x' or 'X', defaulting to 'd'.
102
103       The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
104       output will be '010', from the input of ${15,3,x}
105    */
106
107    string outpart;
108    outpart.reserve(part.size()+5);
109    bool inescape=false;
110
111    for(string::size_type pos = 0; pos < part.size() ; ++pos) {
112      char c=part[pos];
113      if(inescape) {
114        outpart.append(1, c);
115        inescape=false;
116        continue;
117      }
118       
119      if(part[pos]=='\\') {
120        inescape=true;
121        continue;
122      }
123      if(c=='$') {
124        if(pos + 1 == part.size() || part[pos+1]!='{') {  // a trailing $, or not followed by {
125          outpart.append(lexical_cast<string>(d_templatecounter));
126          continue;
127        }
128       
129        // need to deal with { case
130       
131        pos+=2;
132        string::size_type startPos=pos;
133        for(; pos < part.size() && part[pos]!='}' ; ++pos)
134          ;
135       
136        if(pos == part.size()) // partial spec
137          break;
138
139        // we are on the '}'
140
141        string spec(part.c_str() + startPos, part.c_str() + pos);
142        int offset=0, width=0;
143        char radix='d';
144        sscanf(spec.c_str(), "%d,%d,%c", &offset, &width, &radix);  // parse format specifier
145
146        char format[12];
147        snprintf(format, sizeof(format) - 1, "%%0%d%c", width, radix); // make into printf-style format
148
149        char tmp[80];
150        snprintf(tmp, sizeof(tmp)-1, format, d_templatecounter + offset); // and do the actual printing
151        outpart+=tmp;
152      }
153      else
154        outpart.append(1, c);
155    }
156    retline+=outpart;
157  }
158  d_templatecounter+=d_templatestep;
159
160  d_line = retline;
161  return true;
162}
163
164bool ZoneParserTNG::get(DNSResourceRecord& rr) 
165{
166 retry:;
167  if(!getTemplateLine() && !getLine())
168    return false;
169
170  chomp(d_line, " \r\n\x1a");
171
172  parts_t parts;
173  vstringtok(parts, d_line);
174
175  if(parts.empty())
176    goto retry;
177
178  if(d_line[0]=='$') { 
179    string command=makeString(d_line, parts[0]);
180    if(command=="$TTL" && parts.size() > 1)
181      d_defaultttl=makeTTLFromZone(makeString(d_line,parts[1]));
182    else if(command=="$INCLUDE" && parts.size() > 1) {
183      stackFile(unquotify(makeString(d_line, parts[1])));
184    }
185    else if(command=="$ORIGIN" && parts.size() > 1) {
186      d_zonename = toCanonic("", makeString(d_line, parts[1]));
187    }
188    else if(command=="$GENERATE" && parts.size() > 2) {
189      // $GENERATE 1-127 $ CNAME $.0
190      string range=makeString(d_line, parts[1]);
191      d_templatestep=1;
192      d_templatestop=0;
193      sscanf(range.c_str(),"%d-%d/%d", &d_templatecounter, &d_templatestop, &d_templatestep);
194      d_templateline=d_line;
195      parts.pop_front();
196      parts.pop_front();
197
198      d_templateparts=parts;
199      goto retry;
200    }
201    else
202      throw exception("Can't parse zone line '"+d_line+"'");
203    goto retry;
204  }
205
206
207  if(isspace(d_line[0])) 
208    rr.qname=d_prevqname;
209  else {
210    rr.qname=makeString(d_line, parts[0]); 
211    parts.pop_front();
212    if(rr.qname.empty() || rr.qname[0]==';')
213      goto retry;
214  }
215  if(rr.qname=="@")
216    rr.qname=d_zonename;
217  else if(!isCanonical(rr.qname)) {
218    rr.qname.append(1,'.');
219    rr.qname.append(d_zonename);
220  }
221  d_prevqname=rr.qname;
222
223  if(parts.empty()) 
224    throw exception("Line with too little parts");
225
226  // cout<<"Have qname: '"<<rr.qname<<"'\n";
227
228  string nextpart;
229 
230  rr.ttl=d_defaultttl;
231  bool haveTTL=0, haveQTYPE=0;
232  pair<string::size_type, string::size_type> range;
233
234  while(!parts.empty()) {
235    range=parts.front();
236    parts.pop_front();
237    nextpart=makeString(d_line, range);
238    if(nextpart.empty())
239      break;
240
241    if(nextpart.find(';')!=string::npos)
242      break;
243
244    // cout<<"Next part: '"<<nextpart<<"'"<<endl;
245   
246    if(!Utility::strcasecmp(nextpart.c_str(), "IN")) {
247      // cout<<"Ignoring 'IN'\n";
248      continue;
249    }
250    if(!haveTTL && !haveQTYPE && all(nextpart, is_digit())) {
251      rr.ttl=makeTTLFromZone(nextpart);
252      haveTTL=true;
253      // cout<<"ttl is probably: "<<rr.ttl<<endl;
254      continue;
255    }
256    if(haveQTYPE) 
257      break;
258
259    try {
260      rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
261      // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
262      haveQTYPE=1;
263      continue;
264    }
265    catch(...) {
266      cerr<<"Oops, this doesn't look like a qtype, stopping loop\n";
267      break;
268    }
269  }
270  if(!haveQTYPE) 
271    throw exception("Malformed line '"+d_line+"'");
272
273  rr.content=d_line.substr(range.first);
274
275  string::size_type pos=rr.content.rfind(';');
276  if(pos!=string::npos)
277    rr.content.resize(pos);
278
279  if(rr.qtype.getCode()!=QType::TXT && (pos=rr.content.find('('))!=string::npos) {
280    rr.content.resize(pos); // chop off (
281    trim(rr.content);
282    while(getLine()) {
283      chomp(d_line,"\r\n ");
284      pos=d_line.rfind(';');
285      if(pos!=string::npos)
286        d_line.resize(pos);
287
288      trim(d_line);
289     
290      pos=d_line.find(')');
291      if(pos!=string::npos) {
292        d_line.resize(pos);
293        trim(d_line);
294        rr.content+=" "+d_line;
295        break;
296      }
297      rr.content+=" "+d_line;
298    }
299  }
300  vector<string> soaparts;
301  switch(rr.qtype.getCode()) {
302  case QType::MX:
303  case QType::NS:
304  case QType::CNAME:
305  case QType::PTR:
306  case QType::SRV:
307    rr.content=toCanonic(d_zonename, rr.content);
308    break;
309
310  case QType::SOA:
311    stringtok(soaparts, rr.content);
312    if(soaparts.size() > 1) {
313      soaparts[0]=toCanonic(d_zonename, soaparts[0]);
314      soaparts[1]=toCanonic(d_zonename, soaparts[1]);
315    }
316    rr.content.clear();
317    for(string::size_type n = 0; n < soaparts.size(); ++n) {
318      if(n)
319        rr.content.append(1,' ');
320      rr.content+=soaparts[n];
321    }
322  default:;
323  }
324
325  rr.d_place=DNSResourceRecord::ANSWER;
326  return true;
327}
328
329bool ZoneParserTNG::getLine()
330{
331  while(!d_fps.empty()) {
332    char buffer[1024];
333    if(fgets(buffer, 1024, d_fps.top())) {
334      d_line=buffer;
335      return true;
336    }
337    fclose(d_fps.top());
338    d_fps.pop();
339  }
340  return false;
341}
342
343
344#if 0
345int main(int argc, char** argv)
346try
347{
348  reportAllTypes();
349  ZoneParserTNG zpt(argv[1]);
350  DNSResourceRecord rr;
351  while(zpt.get(rr)) {
352  }
353 
354
355}
356catch(...)
357{}
358#endif
Note: See TracBrowser for help on using the browser.