2
PowerDNS Versatile Database Driven Nameserver
3
Copyright (C) 2002 - 2005 PowerDNS.COM BV
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
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
# pragma warning ( disable: 4786 )
37
#include "ahuexception.hh"
42
#include "zoneparser.hh"
44
extern const char *bind_directory;
45
void ZoneParser::setDirectory(const string &dir)
51
void ZoneParser::parse(const string &fname, const string &origin, unsigned int domain_id)
53
d_filename=fname.c_str();
57
zonein=gzopen(fname.c_str(),"r");
59
zonein=gzdopen(STDIN_FILENO,"r");
62
throw AhuException("Unable to open zonefile '"+fname+"': "+stringerror());
75
while(gzgets(fds.top(), cline,sizeof(cline)-1)) {
77
chomp(line," \x1a\r\n");
84
if(line[0]=='$' && (!line.find("$INCLUDE ") || !line.find("$include "))) {
86
stringtok(parts,line," \t\n");
88
throw AhuException("Invalid $INCLUDE statement in zonefile '"+fname+"'");
90
string filename=unquotify(parts[1]);
92
filename=d_dir+"/"+filename;
95
gzFile fp=gzopen(filename.c_str(),"r");
97
throw AhuException("Unable to open zonefile '"+filename+"' included from '"+fname+"': "+stringerror());
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);
106
// if(ferror(fds.top())) {
107
// fclose(fds.top());
109
// throw AhuException("Error reading from file '"+fname+"': "+stringerror());
116
void ZoneParser::parse(const string &fname, const string &origin, vector<Record>&records)
118
d_filename=fname.c_str();
120
gzFile zonein=gzopen(fname.c_str(),"r");
123
throw AhuException("Unable to open zonefile '"+fname+"': "+stringerror());
134
while(!fds.empty()) {
135
while(gzgets(fds.top(),cline, sizeof(cline)-1)) {
137
chomp(line," \x1a\r\n");
144
if(line[0]=='$' && (!line.find("$INCLUDE ") || !line.find("$include "))) {
145
vector<string> parts;
146
stringtok(parts,line," \t\r\n");
148
throw AhuException("Invalid $INCLUDE statement in zonefile '"+fname+"'");
150
string filename=unquotify(parts[1]);
151
gzFile fp=gzopen(filename.c_str(),"r");
153
throw AhuException("Unable to open zonefile '"+filename+"' included from '"+fname+"': "+stringerror());
157
if(eatLine(line,rec))
158
for(vector<Record>::const_iterator i=rec.begin();i!=rec.end();++i)
159
records.push_back(*i);
162
// if(ferror(fds.top())) {
163
// fclose(fds.top());
165
// throw AhuException("Error reading from file '"+fname+"': "+stringerror());
173
void ZoneParser::fillRec(const string &qname, const string &qtype, const string &content, int ttl, int prio, vector<Record>&recs)
179
if(!QType::chartocode(qtype.c_str()))
180
throw AhuException("Unknown qtype '"+qtype+"' on line "+itoa(d_lineno)+" of file '"+d_filename+"'");
185
// cerr<<"filled with type: "<<rec.qtype<<", "<<QType::chartocode(qtype.c_str())<<": "<<content<<endl;
190
void ZoneParser::cutOff(string &line, const string &delim)
192
string::size_type pos=line.find_first_of(delim);
193
if(pos==string::npos)
198
bool ZoneParser::eatLine(const string& line, vector<Record> &rec)
202
static string lastfirstword;
203
string::size_type pos=string::npos;
206
pos=line.find_first_of("(");
207
if(pos!=string::npos) { // this is a line that continues
208
tline=line.substr(0,pos);
212
tline=line; // complete & boring line
214
else { // continuation
216
if(pos==string::npos) { // middle part
221
tline.append(line.substr(0,pos)); // end part, we have a complete line!
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) {
233
if(isspace(tline[0]))
234
tline=lastfirstword+"\t"+tline;
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];
241
// for_each(parts.begin(),parts.end(),print);
243
return parseLine(parts,rec);
246
ZoneParser::~ZoneParser()
251
void ZoneParser::setCallback(callback_t *callback)
256
bool ZoneParser::isNumber(const string &s)
258
for(string::const_iterator i=s.begin();
262
if(*i=='M' || *i=='D' || *i=='H' || *i=='W' || *i=='m' || *i=='d' || *i=='h' || *i=='w') // last character
270
bool ZoneParser::isType(const string &s)
282
bool ZoneParser::isClass(const string &s)
284
return (s.size()==2 && (s=="IN" || s=="CH" || s=="HS" || s=="in" || s=="ch" || s=="hs"));
287
unsigned int ZoneParser::zoneNumber(const string &str)
289
unsigned int val=atoi(str.c_str());
290
char lc=toupper(str[str.length()-1]);
309
throw AhuException("Unable to parse "+d_origin+" time specification '"+str+"' at line "+itoa(d_lineno));
315
/** this parser handles 10 cases (sigh)
316
1) qname TTL CLASS QTYPE *
317
2) qname CLASS TTL QTYPE *
318
3) qname CLASS QTYPE *
322
And then everything again with a space first character, which implies 'same as last name'
325
void ZoneParser::soaCanonic(string &content)
328
stringtok(parts,content," \t");
331
// 'ns.naamserver.net. hostmaster.naamserver.net 2001102501 8H 2H 1W 1D'
332
// FIXME: what about 'ns hostmaster.naamserver.net 2001102501 8H 2H 1W 1D'?
335
for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i,++pos) {
338
newcontent.append(1,' ');
339
newcontent.append( canonic( *i ) );
342
unsigned int val=zoneNumber(*i);
344
newcontent.append(1,' ');
345
newcontent.append(itoa(val));
351
string ZoneParser::expandWord(const string &line, int value)
355
for(string::const_iterator i=line.begin();i!=line.end();++i) {
359
if(!escape && *i=='$') {
360
if(i+2<line.end() && *(i+1)=='{') { // shit
361
string::const_iterator k=(i+=2);
362
while(k++!=line.end() && *k!='}')
365
throw AhuException("Malformed $GENERATE statement");
369
//copy(i,k,back_inserter(spec));
370
for ( string::const_iterator a = i; a != k; ++a )
373
vector<string> partjes;
374
stringtok(partjes,spec,",");
376
throw AhuException("Malformed $GENERATE statement: '"+spec+"'");
378
value+=atoi(partjes[0].c_str());
381
if(partjes.size()>=2)
382
width=atoi(partjes[1].c_str());
383
if(partjes.size()>=3)
390
format.append(1,radix);
392
snprintf(tmp,19,format.c_str(),value);
398
newline.append(itoa(value));
401
newline.append(1,*i);
408
string ZoneParser::canonic(const string& dom)
410
if(dom[dom.size()-1]!='.')
413
return dom.substr(0,dom.size()-1);
418
bool ZoneParser::parseLine(const vector<string>&words, vector<Record>&rec)
426
if(!Utility::strcasecmp(words[0].c_str(),"$ORIGIN") && words.size()>1) {
427
d_origin=canonic(words[1]);
429
else if(!Utility::strcasecmp(words[0].c_str(),"$TTL") && words.size()>1) {
430
d_ttl=zoneNumber(words[1]);
432
else if(!Utility::strcasecmp(words[0].c_str(),"$GENERATE") && words.size()>1) {
433
// $GENERATE 1-127 $ CNAME $.0
434
string range=words[1]; // 1-127 means 1...127 (including 127). 1-127/2 is 1..3..5..
436
stringtok(parts,range,"-/");
437
if(parts.size()<2 || parts.size()>3)
438
throw AhuException("Malformed $GENERATE on line "+itoa(d_lineno)+" of "+d_filename);
440
int start, stop, step=1;
441
start=atoi(parts[0].c_str());
442
stop=atoi(parts[1].c_str());
444
step=atoi(parts[2].c_str());
445
vector<string>newwords;
447
for(int i=start;i<=stop;++i) {
449
for(unsigned int j=2;j<words.size();++j) {
450
newwords.push_back(expandWord(words[j],i));
452
parseLine(newwords, rec);
457
throw AhuException("Unhandled command '"+words[0]+"' on line "+itoa(d_lineno)+" of '"+d_filename+"'");
464
if(words.size()==1 && words[0]==";")
466
throw AhuException("Short line "+itoa(d_lineno)+" in '"+d_filename+"': "+itoa(words.size())+ " words. Probably due to repeated record without domainname");
469
string qname=words[0];
473
if(isNumber(words[1])) // 1 || 4
475
ttl=zoneNumber(words[1]);
476
if(isClass(words[2]))
493
else /* 2 || 3 || 5 */
495
if(!isClass(words[1]))
506
if(isNumber(words[2]))
508
ttl=zoneNumber(words[2]);
514
else if(isType(words[2]))
525
throw AhuException("Funky parse case on line "+itoa(d_lineno));
531
if(qname[qname.size()-1]!='.')
535
// cerr<<qname<<", "<<qclass<<", "<<qtype<<", "<<ttl<<", rest from field "<<cpos<<endl;
537
int left=words.size()-cpos;
540
if((qtype=="MX" && left==2) || (qtype=="SRV" && left==4)){
541
int prio=atoi(words[cpos++].c_str());left--;
542
content=words[cpos++];left--;
545
content+=" "+words[cpos++];
550
if(content[content.size()-1]!='.')
551
content+="."+d_origin;
553
fillRec(qname, qtype, content, ttl, prio,rec);
557
content=words[cpos++];left--;
560
content+=" "+words[cpos++];
562
if(qtype=="MX" || qtype=="CNAME" || qtype=="NS") {
566
if(content[content.size()-1]!='.')
567
content+="."+d_origin;
571
if(qtype=="TXT" && content.size() > 2) { // strip quotes from TXT
573
content=content.substr(1);
574
if(content[content.size()-1]=='"')
575
content.resize(content.size()-1);
578
fillRec(qname, qtype, content,ttl, 0, rec);
582
throw AhuException("No content on line "+itoa(d_lineno));