]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/backends/bind/zoneparser2.cc
fix . zone refreshing bug (we didn't actually import what the root-servers were telli...
[thirdparty/pdns.git] / pdns / backends / bind / zoneparser2.cc
1 /*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 - 2005 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 as
7 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 #ifdef WIN32
20 # pragma warning ( disable: 4786 )
21 #endif // WIN32
22
23 #include <stdio.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <string>
28 #include <vector>
29 #include <iostream>
30 #include <utility>
31 #include <ctype.h>
32 #include <zlib.h>
33 #include <errno.h>
34 #include <stack>
35 #include "utility.hh"
36 #include "misc.hh"
37 #include "ahuexception.hh"
38 #include "qtype.hh"
39 #include <algorithm>
40 using namespace std;
41
42 #include "zoneparser.hh"
43
44 extern const char *bind_directory;
45 void ZoneParser::setDirectory(const string &dir)
46 {
47 d_dir=dir;
48
49 }
50
51 void ZoneParser::parse(const string &fname, const string &origin, unsigned int domain_id)
52 {
53 d_filename=fname.c_str();
54
55 gzFile zonein;
56 if(fname!="-")
57 zonein=gzopen(fname.c_str(),"r");
58 else
59 zonein=gzdopen(STDIN_FILENO,"r");
60
61 if(!zonein)
62 throw AhuException("Unable to open zonefile '"+fname+"': "+stringerror());
63
64 d_origin=origin;
65
66 char cline[2048];
67 string line;
68 d_lineno=0;
69 vector<Record> rec;
70 stack<gzFile> fds;
71 fds.push(zonein);
72
73
74 while(!fds.empty()) {
75 while(gzgets(fds.top(), cline,sizeof(cline)-1)) {
76 line=cline;
77 chomp(line," \x1a\r\n");
78 cutOff(line,";");
79
80 d_lineno++;
81 if(line.empty())
82 continue;
83
84 if(line[0]=='$' && (!line.find("$INCLUDE ") || !line.find("$include "))) {
85 vector<string> parts;
86 stringtok(parts,line," \t\n");
87 if(parts.size()!=2)
88 throw AhuException("Invalid $INCLUDE statement in zonefile '"+fname+"'");
89
90 string filename=unquotify(parts[1]);
91 if(filename[0]!='/')
92 filename=d_dir+"/"+filename;
93
94
95 gzFile fp=gzopen(filename.c_str(),"r");
96 if(!fp)
97 throw AhuException("Unable to open zonefile '"+filename+"' included from '"+fname+"': "+stringerror());
98 fds.push(fp);
99 continue;
100 }
101 if(eatLine(line,rec))
102 for(vector<Record>::const_iterator i=rec.begin();i!=rec.end();++i)
103 d_callback(domain_id, i->name, i->qtype, i->content, i->ttl, i->prio);
104 }
105
106 // if(ferror(fds.top())) {
107 // fclose(fds.top());
108 // fds.pop();
109 // throw AhuException("Error reading from file '"+fname+"': "+stringerror());
110 // }
111 gzclose(fds.top());
112 fds.pop();
113 }
114 }
115
116 void ZoneParser::parse(const string &fname, const string &origin, vector<Record>&records)
117 {
118 d_filename=fname.c_str();
119
120 gzFile zonein=gzopen(fname.c_str(),"r");
121
122 if(!zonein)
123 throw AhuException("Unable to open zonefile '"+fname+"': "+stringerror());
124
125 d_origin=origin;
126
127 char cline[2048];
128 string line;
129 d_lineno=0;
130 vector<Record> rec;
131 stack<gzFile>fds;
132 fds.push(zonein);
133
134 while(!fds.empty()) {
135 while(gzgets(fds.top(),cline, sizeof(cline)-1)) {
136 line=cline;
137 chomp(line," \x1a\r\n");
138 cutOff(line,";");
139
140 d_lineno++;
141 if(line.empty())
142 continue;
143
144 if(line[0]=='$' && (!line.find("$INCLUDE ") || !line.find("$include "))) {
145 vector<string> parts;
146 stringtok(parts,line," \t\r\n");
147 if(parts.size()!=2)
148 throw AhuException("Invalid $INCLUDE statement in zonefile '"+fname+"'");
149
150 string filename=unquotify(parts[1]);
151 gzFile fp=gzopen(filename.c_str(),"r");
152 if(!fp)
153 throw AhuException("Unable to open zonefile '"+filename+"' included from '"+fname+"': "+stringerror());
154 fds.push(fp);
155 continue;
156 }
157 if(eatLine(line,rec))
158 for(vector<Record>::const_iterator i=rec.begin();i!=rec.end();++i)
159 records.push_back(*i);
160 }
161
162 // if(ferror(fds.top())) {
163 // fclose(fds.top());
164 // fds.pop();
165 // throw AhuException("Error reading from file '"+fname+"': "+stringerror());
166 // }
167 gzclose(fds.top());
168 fds.pop();
169 }
170 }
171
172
173 void ZoneParser::fillRec(const string &qname, const string &qtype, const string &content, int ttl, int prio, vector<Record>&recs)
174 {
175 Record rec;
176 rec.name=qname;
177 rec.qtype=qtype;
178
179 if(!QType::chartocode(qtype.c_str()))
180 throw AhuException("Unknown qtype '"+qtype+"' on line "+itoa(d_lineno)+" of file '"+d_filename+"'");
181 rec.content=content;
182 rec.ttl=ttl;
183 rec.prio=prio;
184
185 // cerr<<"filled with type: "<<rec.qtype<<", "<<QType::chartocode(qtype.c_str())<<": "<<content<<endl;
186 recs.push_back(rec);
187 }
188
189
190 void ZoneParser::cutOff(string &line, const string &delim)
191 {
192 string::size_type pos=line.find_first_of(delim);
193 if(pos==string::npos)
194 return;
195 line.resize(pos);
196 }
197
198 bool ZoneParser::eatLine(const string& line, vector<Record> &rec)
199 {
200 rec.clear();
201 static string tline;
202 static string lastfirstword;
203 string::size_type pos=string::npos;
204
205 if(tline.empty()) {
206 pos=line.find_first_of("(");
207 if(pos!=string::npos) { // this is a line that continues
208 tline=line.substr(0,pos);
209 return false;
210 }
211 else
212 tline=line; // complete & boring line
213 }
214 else { // continuation
215 pos=line.find(")");
216 if(pos==string::npos) { // middle part
217 tline.append(line);
218 return false;
219 }
220 else {
221 tline.append(line.substr(0,pos)); // end part, we have a complete line!
222 }
223 }
224
225 // full & unparenthesised line now in tline!
226 // cout<<"line: '"<<tline<<"'"<<endl;
227 if(tline.empty() || tline.find_first_not_of(" \t\n")==string::npos) {
228
229 tline="";
230 return false;
231 }
232
233 if(isspace(tline[0]))
234 tline=lastfirstword+"\t"+tline;
235
236 vector<string> parts;
237 stringtok(parts,tline," \t"); // we used to strip " here
238 if(parts[0][0]!='$' && !isspace(parts[0][0]))
239 lastfirstword=parts[0];
240
241 // for_each(parts.begin(),parts.end(),print);
242 tline="";
243 return parseLine(parts,rec);
244 }
245
246 ZoneParser::~ZoneParser()
247 {
248
249 }
250
251 void ZoneParser::setCallback(callback_t *callback)
252 {
253 d_callback=callback;
254 }
255
256 bool ZoneParser::isNumber(const string &s)
257 {
258 for(string::const_iterator i=s.begin();
259 i!=s.end();
260 ++i) {
261 if(i+1==s.end())
262 if(*i=='M' || *i=='D' || *i=='H' || *i=='W' || *i=='m' || *i=='d' || *i=='h' || *i=='w') // last character
263 continue;
264 if(!isdigit(*i))
265 return false;
266 }
267 return true;
268 }
269
270 bool ZoneParser::isType(const string &s)
271 {
272 if(isNumber(s))
273 return false;
274
275 if(isClass(s))
276 return false;
277
278 return true;
279 }
280
281 bool ZoneParser::isClass(const string &s)
282 {
283 return (s.size()==2 && (s=="IN" || s=="CH" || s=="HS" || s=="in" || s=="ch" || s=="hs"));
284 }
285
286 unsigned int ZoneParser::zoneNumber(const string &str)
287 {
288 unsigned int val=atoi(str.c_str());
289 char lc=toupper(str[str.length()-1]);
290 if(!isdigit(lc))
291 switch(lc) {
292 case 'H':
293 val*=3600;
294 break;
295 case 'D':
296 val*=3600*24;
297 break;
298 case 'W':
299 val*=3600*24*7;
300 break;
301 case 'M':
302 val*=3600*24*7*4;
303 break;
304 case 'Y': // ? :-)
305 val*=3600*24*365;
306 break;
307 default:
308 throw AhuException("Unable to parse "+d_origin+" time specification '"+str+"' at line "+itoa(d_lineno));
309 }
310 return val;
311
312 }
313
314 /** this parser handles 10 cases (sigh)
315 1) qname TTL CLASS QTYPE *
316 2) qname CLASS TTL QTYPE *
317 3) qname CLASS QTYPE *
318 4) qname TTL QTYPE *
319 5) qname QTYPE *
320
321 And then everything again with a space first character, which implies 'same as last name'
322 */
323
324 void ZoneParser::soaCanonic(string &content)
325 {
326 vector<string>parts;
327 stringtok(parts,content," \t");
328 int pos=0;
329
330 // 'ns.naamserver.net. hostmaster.naamserver.net 2001102501 8H 2H 1W 1D'
331 // FIXME: what about 'ns hostmaster.naamserver.net 2001102501 8H 2H 1W 1D'?
332
333 string newcontent;
334 for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i,++pos) {
335 if(pos<3) {
336 if(pos)
337 newcontent.append(1,' ');
338 newcontent.append( canonic( *i ) );
339 }
340 else {
341 unsigned int val=zoneNumber(*i);
342
343 newcontent.append(1,' ');
344 newcontent.append(itoa(val));
345 }
346 }
347 content=newcontent;
348 }
349
350 string ZoneParser::expandWord(const string &line, int value)
351 {
352 string newline;
353 bool escape=false;
354 for(string::const_iterator i=line.begin();i!=line.end();++i) {
355 if(*i=='\\')
356 escape=true;
357 else{
358 if(!escape && *i=='$') {
359 if(i+2<line.end() && *(i+1)=='{') { // shit
360 string::const_iterator k=(i+=2);
361 while(k++!=line.end() && *k!='}')
362 ;
363 if(k==line.end())
364 throw AhuException("Malformed $GENERATE statement");
365
366 string spec;
367
368 //copy(i,k,back_inserter(spec));
369 for ( string::const_iterator a = i; a != k; ++a )
370 spec += *a;
371
372 vector<string> partjes;
373 stringtok(partjes,spec,",");
374 if(partjes.empty())
375 throw AhuException("Malformed $GENERATE statement: '"+spec+"'");
376
377 value+=atoi(partjes[0].c_str());
378 int width=0;
379 char radix='d';
380 if(partjes.size()>=2)
381 width=atoi(partjes[1].c_str());
382 if(partjes.size()>=3)
383 radix=partjes[2][0];
384
385 char tmp[20];
386 string format;
387 format="%0";
388 format+=itoa(width);
389 format.append(1,radix);
390
391 snprintf(tmp,19,format.c_str(),value);
392
393 newline.append(tmp);
394 i=k;
395 }
396 else
397 newline.append(itoa(value));
398 }
399 else
400 newline.append(1,*i);
401 escape=false;
402 }
403 }
404 return newline;
405 }
406
407 string ZoneParser::canonic(const string& dom)
408 {
409 if(dom[dom.size()-1]!='.')
410 return dom;
411
412 return dom.substr(0,dom.size()-1);
413
414 }
415
416
417 bool ZoneParser::parseLine(const vector<string>&words, vector<Record>&rec)
418 {
419 int cpos=0;
420 if(!words.size())
421 return false;
422
423 if(words[0][0]=='$')
424 {
425 if(!Utility::strcasecmp(words[0].c_str(),"$ORIGIN") && words.size()>1) {
426 d_origin=canonic(words[1]);
427 }
428 else if(!Utility::strcasecmp(words[0].c_str(),"$TTL") && words.size()>1) {
429 d_ttl=zoneNumber(words[1]);
430 }
431 else if(!Utility::strcasecmp(words[0].c_str(),"$GENERATE") && words.size()>1) {
432 // $GENERATE 1-127 $ CNAME $.0
433 string range=words[1]; // 1-127 means 1...127 (including 127). 1-127/2 is 1..3..5..
434 vector<string>parts;
435 stringtok(parts,range,"-/");
436 if(parts.size()<2 || parts.size()>3)
437 throw AhuException("Malformed $GENERATE on line "+itoa(d_lineno)+" of "+d_filename);
438
439 int start, stop, step=1;
440 start=atoi(parts[0].c_str());
441 stop=atoi(parts[1].c_str());
442 if(parts.size()==3)
443 step=atoi(parts[2].c_str());
444 vector<string>newwords;
445
446 for(int i=start;i<=stop;++i) {
447 newwords.clear();
448 for(unsigned int j=2;j<words.size();++j) {
449 newwords.push_back(expandWord(words[j],i));
450 }
451 parseLine(newwords, rec);
452 }
453 return true;
454 }
455 else {
456 throw AhuException("Unhandled command '"+words[0]+"' on line "+itoa(d_lineno)+" of '"+d_filename+"'");
457 }
458
459 return false;
460
461 }
462 if(words.size()<3) {
463 if(words.size()==1 && words[0]==";")
464 return false;
465 throw AhuException("Short line "+itoa(d_lineno)+" in '"+d_filename+"': "+itoa(words.size())+ " words. Probably due to repeated record without domainname");
466 }
467
468 string qname=words[0];
469 string qclass="IN";
470 int ttl=d_ttl;
471 string qtype="NONE";
472 if(isNumber(words[1])) // 1 || 4
473 {
474 ttl=zoneNumber(words[1]);
475 if(isClass(words[2]))
476 {
477 // cout<<1<<endl;
478 qclass=words[2];
479 qtype=words[3];
480 cpos=4;
481 // 1
482 }
483 else
484 {
485 // cout<<4<<endl;
486
487 qtype=words[2];
488 cpos=3;
489 // 4
490 }
491 }
492 else /* 2 || 3 || 5 */
493 {
494 if(!isClass(words[1]))
495 {
496
497 qtype=words[1];
498 cpos=2;
499 // cout<<5<<endl;
500 // 5
501 }
502 else // 2 || 3
503 {
504 qclass=words[1];
505 if(isNumber(words[2]))
506 {
507 ttl=zoneNumber(words[2]);
508 qtype=words[3];
509 // cout<<2<<endl;
510 cpos=4;
511 // 2
512 }
513 else if(isType(words[2]))
514 {
515 qtype=words[2];
516 // cout<<4<<endl;
517 cpos=3;
518 // 4
519 }
520 }
521
522 }
523 if(!cpos) {
524 throw AhuException("Funky parse case on line "+itoa(d_lineno));
525 }
526
527 if(qname=="@")
528 qname=d_origin;
529 else
530 if(qname[qname.size()-1]!='.')
531 qname+="."+d_origin;
532
533
534 // cerr<<qname<<", "<<qclass<<", "<<qtype<<", "<<ttl<<", rest from field "<<cpos<<endl;
535
536 int left=words.size()-cpos;
537 string content;
538
539 if((qtype=="MX" && left==2) || (qtype=="SRV" && left==4)){
540 int prio=atoi(words[cpos++].c_str());left--;
541 content=words[cpos++];left--;
542
543 while(left--)
544 content+=" "+words[cpos++];
545
546 if(content=="@")
547 content=d_origin;
548 else
549 if(content[content.size()-1]!='.')
550 content+="."+d_origin;
551
552 fillRec(qname, qtype, content, ttl, prio,rec);
553 return true;
554 }
555 else if(left) {
556 content=words[cpos++];left--;
557
558 while(left--)
559 content+=" "+words[cpos++];
560
561 if(qtype=="MX" || qtype=="CNAME" || qtype=="NS") {
562 if(content=="@")
563 content=d_origin;
564 else
565 if(content[content.size()-1]!='.')
566 content+="."+d_origin;
567 }
568 if(qtype=="SOA")
569 soaCanonic(content);
570 if(qtype=="TXT" && content.size() > 2) { // strip quotes from TXT
571 if(content[0]=='"')
572 content=content.substr(1);
573 if(content[content.size()-1]=='"')
574 content.resize(content.size()-1);
575 }
576
577 fillRec(qname, qtype, content,ttl, 0, rec);
578 return true;
579 }
580 else {
581 throw AhuException("No content on line "+itoa(d_lineno));
582 }
583 return false;
584 }
585
586