#include #include #include #include #include "dnsconf.h" #include "internal.h" /* fgets with support for continuation line */ static char *record_fgets( char *buf, int size, FILE_CFG *fin) { char *ret = NULL; bool cont = false; bool firstline = true; // We keep comments only on single line // entry while (fgets(buf,size,fin)!=NULL){ if (ret == NULL) ret = buf; char *pt = str_skip (buf); if (firstline && *pt == ';'){ break; }else{ char *ptp = strchr (buf,';'); if (ptp != NULL) *ptp = '\0'; // Strip comments char *ptc = strchr (buf,'('); char *pte = strchr (buf,')'); //if (ptp != NULL) *ptp = ';'; firstline = false; if (ptc != NULL){ /* #Specification: bind / zone file format There is a weird case in zone file. The parenthese is used as a continuation line marker. It seems only applicable in SOA records. Anyone knows what is the standard ? For now, we check if it is as the end of the line */ char *pt = ptc+1; while (*pt != '\0' && *pt <= ' ') pt++; if (*pt == '\0'){ // Openning parenthese *ptc = ' '; cont = true; } }else if (pte != NULL){ // Closing parenthese *pte = ' '; cont = false; } if (!cont) break; int len = strlen(buf); buf += len; size -= len; } } return ret; } PUBLIC ORIGIN::ORIGIN (const char *_origin) { origin.setfrom (_origin); } PUBLIC void ORIGIN::print(bool save_ori, TBFILE &tbf) const { // We don't put $ORIGIN at the beginning of the file // because it is implied if (save_ori) fprintf (tbf.cur,"$ORIGIN %s.\n",origin.get()); tbrec.save(tbf); } /* Locate all IP number in use in that area (sub origin) of a domain. Return the number added to adrs */ PUBLIC int ORIGIN::getalladr(IP_ADDRS &adrs) { int ret = 0; int n = tbrec.getnb(); const char *dom = origin.get(); for (int i=0; iis(RTYPE_A)){ RECORD_IN_A *in = (RECORD_IN_A*)rec; adrs.add (new IP_ADDR(in->addr)); ret++; }else if (rec->is(RTYPE_PTR)){ RECORD_IN_PTR *in = (RECORD_IN_PTR*)rec; char revip[100]; sprintf (revip,"%s.%s",in->addr.get(),dom); IP_ADDR *ra = new IP_ADDR(revip); ra->reverse(); adrs.add(ra); ret++; } } return ret; } /* Return != if any component of the ORIGIN was modified */ PUBLIC int ORIGIN::was_modified() { int ret = ARRAY_OBJ::was_modified(); if (!ret){ ret = tbrec.was_modified(); } return ret; } PRIVATE int ORIGINS::parsespecial( const char *key, const char *pt, const char *first_origin, TBFILE &tbf, ORIGIN *&ori) { int ret = 0; pt = str_skip(pt); char arg[200]; pt = str_copyword (arg,pt,sizeof(arg)); if (stricmp(key,"$ORIGIN")==0){ /* #specification: dnsconf / $origin / convert to absolute Internally, all $origin are managed as absolute name, but without a trailing dot. The dot is appended at rewritting time. This has the side effect of converting relative $origin statement (not too common, and often a mistake) to fully absolute names. */ if (!dns_isabs(arg)){ strcat (arg,"."); strcat (arg,first_origin); }else{ dns_stripabs(arg); } ori = new ORIGIN(arg); add (ori); }else if (stricmp(key,"$INCLUDE")==0){ ori->tbrec.add (new RECORD_INCLUDE(arg)); ret = tbf.fopen (arg,"r") != NULL ? 0 : -1; }else if (stricmp(key,"$TTL")==0){ ori->tbrec.add (new RECORD_TTL(arg)); }else{ ret = -1; } return ret; } /* Complete the parsing of the line, pt point to the TTL maybe Return NULL if any error or a new RECORD. */ PRIVATE int ORIGINS::parseend ( char *pt, RECORD_PARSE &p, ORIGIN *ori) { int ret = -1; pt = str_skip(pt); if (isdigit(*pt)){ pt = str_copyword (p.ttl,pt,sizeof(p.ttl)); pt = str_skip(pt); } char keyword[200]; pt = str_copyword (keyword,pt,sizeof(keyword)); if (stricmp(keyword,"IN")==0){ /* #Specification: RECORD / format / IN keyword We assume the IN keyword is optionnal. So far, this is the only record class type available anyway. */ pt = str_skip(pt); pt = str_copyword (keyword,pt,sizeof(keyword)); } pt = str_skip(pt); const char *startf2 = pt; pt = str_copyword (p.f2,pt,sizeof(p.f2)); pt = str_skip(pt); pt = str_copyword (p.f3,pt,sizeof(p.f3)); pt = str_skip(pt); pt = str_copyword (p.f4,pt,sizeof(p.f4)); pt = str_skip(pt); pt = str_copyword (p.f5,pt,sizeof(p.f5)); pt = str_skip(pt); pt = str_copyword (p.f6,pt,sizeof(p.f6)); pt = str_skip(pt); pt = str_copyword (p.f7,pt,sizeof(p.f7)); pt = str_skip(pt); pt = str_copyword (p.f8,pt,sizeof(p.f8)); RECORD *o = NULL; if (stricmp(keyword,"A")==0){ o = new RECORD_IN_A(p); }else if (stricmp(keyword,"MX")==0){ o = new RECORD_IN_MX(p); }else if (stricmp(keyword,"NS")==0){ o = new RECORD_IN_NS(p); }else if (stricmp(keyword,"PTR")==0){ o = new RECORD_IN_PTR(p); }else if (stricmp(keyword,"CNAME")==0){ o = new RECORD_IN_CNAME(p); }else if (stricmp(keyword,"SOA")==0){ o = new RECORD_IN_SOA(p); }else if (stricmp(keyword,"HINFO")==0){ o = new RECORD_IN_HINFO(p.f1,startf2); }else if (stricmp(keyword,"RP")==0){ o = new RECORD_IN_RP(p.f1,startf2); }else if (stricmp(keyword,"TXT")==0){ o = new RECORD_IN_TXT(p.f1,startf2); } if (o != NULL){ ori->tbrec.add(o); ret = 0; }else{ ret = -1; } return ret; } PUBLIC ORIGIN *ORIGINS::getitem(int no) const { return (ORIGIN*)ARRAY::getitem(no); } /* Read a file with a bunch of IN records and manage those. */ PUBLIC int ORIGINS::read ( const char *named_dir, const char *fname, const char *first_origin, bool extract) { /* Reading a record file is complicate. It may contain several $origin statement and several $include statement. We maintain the records as a set of several origin related list. In those list, we maintain special records indicating the beginning and end of an include file scope. */ // We force the defaults zone origin now, making sure there is // at least one origin so record could be dispatch later // See the comment at the end ORIGIN *ori = new ORIGIN(first_origin); add (ori); int ret = -1; TBFILE tbf(named_dir,extract); tbf.fopen(fname,"r"); while (tbf.cur != NULL){ char buf[10000]; char last[200]; strcpy (last,"@"); ret = 0; while (record_fgets(buf,sizeof(buf)-1,tbf.cur)!=NULL){ strip_end (buf); RECORD_PARSE parse; if (buf[0] > ' ' && buf[0] != ';'){ char *pt = str_copyword (parse.f1,buf,sizeof(parse.f1)); if (parse.f1[0] == '$'){ ret = parsespecial (parse.f1,pt,first_origin ,tbf,ori); if (stricmp(parse.f1,"$origin")==0){ strcpy (last,"@"); } }else{ strcpy (last,parse.f1); ret = parseend (pt,parse,ori); } }else if (buf[0] != '\0'){ char *pt = str_skip (buf); if (*pt == ';'){ ori->tbrec.add (new RECORD_COMMENT(buf)); }else{ strcpy (parse.f1,last); ret = parseend (pt,parse,ori); } } } tbf.fclose (); if (tbf.cur != NULL) ori->tbrec.add (new RECORD_END_INCLUDE); } /* Many people believed that a zone file must start with an $origin statement. this is not necessary and confuse a little linuxconf. Linuxconf already have "logically" inserted the default origin. If the default origin was already at the beginning of the file this will create in memory # $origin domain $origin domain ... # Later, when new record will be inserted by linuxconf, this will create # $origin domain new stuff new stuff $origin domain ... # And when linuxconf will write the zone file back, this will create # new stuff new stuff $origin domain ... # While this is valid, this left those convinced that the $origin must be first a strange feeling. For this reason, linuxconf filtered this double $origin while reading and output a comment at the beginning of the file */ if (getnb() >= 2){ ORIGIN *first = getitem(0); ORIGIN *second = getitem(1); if (first->tbrec.getnb() == 0 && first->origin.cmp(second->origin)==0){ remove_del (0); } } return ret; } /* Save in a file a bunch of IN records. */ PUBLIC int ORIGINS::save ( const char *named_dir, const char *fname) const { int ret = -1; TBFILE tbf(named_dir,false); if (tbf.fopen(fname,"w") != NULL){ ret = 0; for (int i=0; iprint (i != 0 ,tbf); } } return ret; }