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