/* conf-parser.c - Parser for pdnsd config files. Based on the files conf-lex.l and conf-parse.y written by Thomas Moestl. This version was rewritten in C from scratch by Paul A. Rombouts and doesn't require (f)lex or yacc/bison. Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011 Paul A. Rombouts. This file is part of the pdnsd package. pdnsd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. pdnsd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with pdnsd; see the file COPYING. If not, see . */ #include #include "ipvers.h" #include #include #include #include #include #include #include #include #if defined(HAVE_STRUCT_IFREQ) #include #endif #include "conff.h" #include "consts.h" #include "cache.h" #include "dns.h" #include "helpers.h" #include "rr_types.h" #include "netdev.h" #include "conf-keywords.h" #include "conf-parser.h" /* Check that include files are not nested deeper than MAXINCLUDEDEPTH, as a precaution against infinite recursion. */ #define MAXINCLUDEDEPTH 100 static char *report_error (const char *conftype, unsigned linenr, const char *msg) { char *retval; if(linenr) { if(asprintf(&retval, "Error in %s (line %u): %s",conftype,linenr,msg)<0) retval=NULL; } else { if(asprintf(&retval, "Error in %s: %s",conftype,msg)<0) retval=NULL; } return retval; } static char *report_errorf (const char *conftype, unsigned linenr, const char *frm,...) printfunc(3, 4); static char *report_errorf (const char *conftype, unsigned linenr, const char *frm,...) { char *msg,*retval; int mlen; va_list va; va_start(va,frm); mlen=vasprintf(&msg,frm,va); va_end(va); if(mlen<0) return NULL; retval=report_error(conftype,linenr,msg); free(msg); return retval; } /* return pointer to next character in linebuffer after skipping blanks and comments */ static char* getnextp(char **buf, size_t *n, FILE* in, char *p, unsigned *linenr, char **errstr) { if(!p) goto nextline; tryagain: if(!*p) { nextline: do { if(!in || getline(buf,n,in)<0) { *errstr=NULL; return NULL; } ++*linenr; p=*buf; } while(!*p); } if(isspace(*p)) { ++p; goto tryagain; } if(*p=='#') { skip_rest_of_line: if(*linenr) goto nextline; else { p=strchr(p,'\n'); if(p) { ++p; goto tryagain; } else goto nextline; } } if(*p=='/') { if(*(p+1)=='/') goto skip_rest_of_line; if(*(p+1)=='*') { int lev=1; p +=2; for(;;) { while(*p) { if(*p=='/' && *(p+1)=='*') { ++lev; p +=2; continue; } else if(*p=='*' && *(p+1)=='/') { p +=2; if(--lev==0) goto tryagain; continue; } ++p; } if(!in || getline(buf,n,in)<0) { *errstr="comment without closing */"; return NULL; } ++*linenr; p=*buf; } } } return p; } static char translescapedchar(char c) { switch(c) { case 'f': return '\f'; case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; case 'v': return '\v'; } return c; } /* Scan a buffer for a string and copy the decoded (i.e. unescaped) version into another buffer. A string either begins after and ends before a double-quote ("), or simply consists of a sequence of "non-special" characters, starting at the current position. A back-slash (\) acts as an escape character, preventing any character following it from terminating the string. Thus, for example, back-slash double-quote (\") may be used to include double-quotes in a string. A number of escape sequences are interpreted as in C, e.g. \t, \n, \r yield control-chars as in C. char **curp should point to the position in the buffer where the scanning should begin. It will be updated to point to the first character past the scanned string. char *outbuf is used to store the decoded string. size_t outbufsz should be the size of outbuf. The return value is the length of the decoded string, unless an error occurs, in which case -1 is returned and *errstr is assigned an error message. The returned length may be larger than outbufsz, in which case the buffer is filled with only the first outbufsz chars of the string. */ static int scan_string(char **curp,char *outbuf, unsigned outbufsz, char **errstr) { char *cur=*curp; unsigned i=0; if(*cur=='"') { /* Double-quoted string. */ ++cur; /* Skip opening quote. */ for(;; ++i,++cur) { if(!*cur) goto noclosingquote; if(*cur=='"') break; if(*cur=='\\') { if(!*++cur) goto nofollowingchar; if(i=sizeof(buf)) \ {CLEANUP_HANDLERS; goto string_too_long;} \ } # define REPORT_ERROR(msg) (*errstr=report_error(conftype,linenr,msg)) # if !defined(CPP_C99_VARIADIC_MACROS) /* GNU C Macro Varargs style. */ # define REPORT_ERRORF(args...) (*errstr=report_errorf(conftype,linenr,args)) #else /* ANSI C99 style. */ # define REPORT_ERRORF(...) (*errstr=report_errorf(conftype,linenr,__VA_ARGS__)) # endif # define PARSERROR {CLEANUP_HANDLERS; goto free_linebuf_return;} # define OUTOFMEMERROR {CLEANUP_HANDLERS; goto out_of_memory;} # define CLEANUP_GOTO(lab) {CLEANUP_HANDLERS; goto lab;} *errstr=NULL; if(in) { linebuf=malloc(buflen); if(!linebuf) { /* If malloc() just failed, allocating space for an error message is unlikely to succeed. */ return 0; } if(global) conftype="config file"; else conftype="include file"; } else conftype="config string"; p=prestr; while((p=getnextp(&linebuf,&buflen,in,p,&linenr,&getnextperr))) { if(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); sechdr=lookup_keyword(ps,len,section_headers); if(!sechdr) { REPORT_ERRORF("invalid section header: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='{') goto expected_bropen; ++p; SKIP_BLANKS(p); switch(sechdr) { case GLOBAL: if(!global) { REPORT_ERROR(in?"global section not allowed in include file": "global section not allowed in eval string"); PARSERROR; } while(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); option=lookup_keyword(ps,len,global_options); if(!option) { REPORT_ERRORF("invalid option for global section: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='=') goto expected_equals; ++p; SKIP_BLANKS(p); switch(option) { pdnsd_a *ipaddrp; case PERM_CACHE: if (isalpha(*p)) { int cnst; SCAN_ALPHANUM(ps,p,len); cnst=lookup_const(ps,len); if(cnst==C_OFF) { global->perm_cache=0; } else goto bad_perm_cache_option; } else if(isdigit(*p)) { global->perm_cache=strtol(p,&p,0); } else { bad_perm_cache_option: REPORT_ERROR("bad qualifier in perm_cache= option."); PARSERROR; } break; case CACHE_DIR: SCAN_STRING(p,strbuf,len); STRNDUP(global->cache_dir,strbuf,len); break; case SERVER_PORT: SCAN_UNSIGNED_NUM(global->port,p,"server_port option") break; case OUTGOING_IP: ipaddrp= &global->out_a; goto scan_ip_or_interface; case SERVER_IP: ipaddrp= &global->a; scan_ip_or_interface: SCAN_STRING(p,strbuf,len); { const char *err; if ((err=parse_ip(strbuf,ipaddrp))) { #if defined(HAVE_STRUCT_IFREQ) && defined(IFNAMSIZ) && defined(SIOCGIFADDR) if(!strcmp(err,"bad IP address") && lenipv4= ((struct sockaddr_in *)&req.ifr_addr)->sin_addr; # endif # ifdef ENABLE_IPV6 ELSE_IPV6 ipaddrp->ipv6= ((struct sockaddr_in6 *)&req.ifr_addr)->sin6_addr; # endif close(fd); } else { REPORT_ERRORF("Failed to get IP address of %s: %s",req.ifr_name,strerror(errno)); if(fd!=-1) close(fd); PARSERROR; } } else #endif { REPORT_ERRORF("%s for the %s= option.",err,option==SERVER_IP?"server_ip":"outgoing_ip"); PARSERROR; } } } break; case SCHEME_FILE: SCAN_STRING(p,strbuf,len); STRNDUP(global->scheme_file, strbuf,len); break; case LINKDOWN_KLUGE: ASSIGN_ON_OFF(global->lndown_kluge,p,C_ON,"bad qualifier in linkdown_kluge= option."); break; case MAX_TTL: SCAN_TIMESECS(global->max_ttl,p,"max_ttl option"); break; case MIN_TTL: SCAN_TIMESECS(global->min_ttl,p,"min_ttl option"); break; case RUN_AS: SCAN_STRING(p,strbuf,len); STRNCP(global->run_as, strbuf,len, "run_as"); break; case STRICT_SETUID: ASSIGN_ON_OFF(global->strict_suid, p,C_ON,"bad qualifier in strict_setuid= option."); break; case USE_NSS: ASSIGN_ON_OFF(global->use_nss, p,C_ON,"bad qualifier in use_nss= option."); break; case PARANOID: ASSIGN_ON_OFF(global->paranoid, p,C_ON,"bad qualifier in paranoid= option."); break; case IGNORE_CD: { int ignore_cd; ASSIGN_ON_OFF(ignore_cd, p,C_ON,"bad qualifier in ignore_cd= option."); fprintf(stderr, "Warning: ignore_cd option in configuration file is obsolete and currently has no effect.\n"); } break; case STATUS_CTL: { int cnst; ASSIGN_CONST(cnst, p,cnst==C_ON || cnst==C_OFF ,"bad qualifier in status_pipe= option."); if(!cmdline.stat_pipe) global->stat_pipe=(cnst==C_ON); } break; case DAEMON: { int cnst; ASSIGN_CONST(cnst, p,cnst==C_ON || cnst==C_OFF ,"bad qualifier in daemon= option."); if(!cmdline.daemon) global->daemon=(cnst==C_ON); } break; case C_TCP_SERVER: { int cnst; ASSIGN_CONST(cnst, p,cnst==C_ON || cnst==C_OFF ,"bad qualifier in tcp_server= option."); if(!cmdline.notcp) { global->notcp=(cnst==C_OFF); #ifdef NO_TCP_SERVER if(!global->notcp) { REPORT_ERROR("pdnsd was compiled without TCP server support. tcp_server=on is not allowed."); PARSERROR; } #endif } } break; case PID_FILE: SCAN_STRING(p,strbuf,len); if(!cmdline.pidfile) {STRNDUP(global->pidfile,strbuf,len);} break; case C_VERBOSITY: { int val; SCAN_UNSIGNED_NUM(val,p,"verbosity option"); if(!cmdline.verbosity) global->verbosity=val; } break; case C_QUERY_METHOD: { int cnst; ASSIGN_CONST(cnst,p,cnst==TCP_ONLY || cnst==UDP_ONLY || cnst==TCP_UDP || cnst==UDP_TCP,"bad qualifier in query_method= option."); #ifdef NO_TCP_QUERIES if (cnst==TCP_ONLY) { REPORT_ERROR("the tcp_only option is only available when pdnsd is compiled with TCP support."); PARSERROR; } else #endif #ifdef NO_UDP_QUERIES if (cnst==UDP_ONLY) { REPORT_ERROR("the udp_only option is only available when pdnsd is compiled with UDP support."); PARSERROR; } else #endif #if defined(NO_TCP_QUERIES) || defined(NO_UDP_QUERIES) if (cnst==TCP_UDP) { REPORT_ERROR("the tcp_udp option is only available when pdnsd is compiled with both TCP and UDP support."); PARSERROR; } else if (cnst==UDP_TCP) { REPORT_ERROR("the udp_tcp option is only available when pdnsd is compiled with both TCP and UDP support."); PARSERROR; } else #endif if(!cmdline.query_method) global->query_method=cnst; } break; case RUN_IPV4: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_ON || cnst==C_OFF,"bad qualifier in run_ipv4= option."); #ifndef ENABLE_IPV4 if(cnst==C_ON) { REPORT_ERROR("You can only set run_ipv4=on when pdnsd is compiled with IPv4 support."); PARSERROR; } #endif #ifndef ENABLE_IPV6 if(cnst==C_OFF) { REPORT_ERROR("You can only set run_ipv4=off when pdnsd is compiled with IPv6 support."); PARSERROR; } #endif #if defined(ENABLE_IPV4) && defined(ENABLE_IPV6) if(!cmdlineipv) { run_ipv4=(cnst==C_ON); cmdlineipv=-1; } else if(cmdlineipv<0 && run_ipv4!=(cnst==C_ON)) { REPORT_ERROR(cmdlineipv==-1? "IPv4/IPv6 conflict: you are trying to set run_ipv4 to a value that conflicts with a previous run_ipv4 setting.": "You must set the run_ipv4 option before specifying IP addresses."); PARSERROR; } #endif } break; case IPV4_6_PREFIX: SCAN_STRING(p,strbuf,len); #ifdef ENABLE_IPV6 if(!cmdline.prefix) { if(inet_pton(AF_INET6,strbuf,&global->ipv4_6_prefix)<=0) { REPORT_ERROR("ipv4_6_prefix: argument not a valid IPv6 address."); PARSERROR; } } #else fprintf(stderr,"pdnsd was compiled without IPv6 support. ipv4_6_prefix option in config file will be ignored.\n"); #endif break; case C_DEBUG: { int cnst; ASSIGN_CONST(cnst, p,cnst==C_ON || cnst==C_OFF ,"bad qualifier in debug= option."); if(!cmdline.debug) { global->debug=(cnst==C_ON); #if !DEBUG if(global->debug) fprintf(stderr,"pdnsd was compiled without debugging support. debug=on has no effect.\n"); #endif } } break; case C_CTL_PERMS: SCAN_UNSIGNED_NUM(global->ctl_perms, p,"ctl_perms option"); break; case C_PROC_LIMIT: SCAN_UNSIGNED_NUM(global->proc_limit, p,"proc_limit option"); break; case C_PROCQ_LIMIT: SCAN_UNSIGNED_NUM(global->procq_limit, p,"procq_limit option"); break; case TCP_QTIMEOUT: SCAN_TIMESECS(global->tcp_qtimeout, p,"tcp_qtimeout option"); break; case TIMEOUT: SCAN_TIMESECS(global->timeout, p,"global timeout option"); break; case C_PAR_QUERIES: { int val; SCAN_UNSIGNED_NUM(val, p,"par_queries option"); if(val<=0) { REPORT_ERROR("bad value for par_queries."); PARSERROR; } else { global->par_queries=val; } } break; case C_RAND_RECS: ASSIGN_ON_OFF(global->rnd_recs, p,C_ON,"bad qualifier in randomize_recs= option."); break; case NEG_TTL: SCAN_TIMESECS(global->neg_ttl, p,"neg_ttl option"); break; case NEG_RRS_POL: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_ON || cnst==C_OFF || cnst==C_DEFAULT || cnst==C_AUTH, "bad qualifier in neg_rrs_pol= option."); global->neg_rrs_pol=cnst; } break; case NEG_DOMAIN_POL: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_ON || cnst==C_OFF || cnst==C_AUTH,"bad qualifier in neg_domain_pol= option."); global->neg_domain_pol=cnst; } break; case QUERY_PORT_START: { int val; if(isalpha(*p)) { int cnst; SCAN_ALPHANUM(ps,p,len); cnst=lookup_const(ps,len); if(cnst==C_NONE) val=-1; else goto bad_port_start_option; } else if(isdigit(*p)) { val=strtol(p,&p,0); if(val>65535) { REPORT_ERROR("value for query_port_start out of range."); PARSERROR; } else if(val<1024) fprintf(stderr,"Warning: query_port_start=%i but source ports <1204 can only be used as root.\n", val); } else { bad_port_start_option: REPORT_ERROR("bad qualifier in query_port_start= option."); PARSERROR; } global->query_port_start=val; } break; case QUERY_PORT_END: { int val; SCAN_UNSIGNED_NUM(val,p,"query_port_end option"); if(val>65535) { REPORT_ERROR("value for query_port_end out of range."); PARSERROR; } global->query_port_end=val; } break; case UDP_BUFSIZE: { int val; SCAN_UNSIGNED_NUM(val,p,"udpbufsize"); if(val<512 || val>65535-(20+8)) { REPORT_ERROR("value for udpbufsize out of range."); PARSERROR; } global->udpbufsize=val; } break; case DELEGATION_ONLY: SCAN_STRING_LIST(&global->deleg_only_zones,p,strbuf,len,zone_add) break; default: /* we should never get here */ goto internal_parse_error; } /* end of switch(option) */ SKIP_BLANKS(p); if(*p!=';') goto expected_semicolon; ++p; SKIP_BLANKS(p); } if(*p!='}') goto expected_closing_brace; if (global->query_port_end < global->query_port_start) { REPORT_ERROR("query_port_end may not be smaller than query_port_start."); PARSERROR; } break; case SERVER: { servparm_t server; if(!servers) { REPORT_ERROR(in?"server section not allowed in include file": "server section not allowed in eval string"); PARSERROR; } server=serv_presets; # undef CLEANUP_HANDLER # define CLEANUP_HANDLER (free_servparm(&server)) while(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); option=lookup_keyword(ps,len,server_options); if(!option) { REPORT_ERRORF("invalid option for server section: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='=') CLEANUP_GOTO(expected_equals); ++p; SKIP_BLANKS(p); switch(option) { case IP: SCAN_STRING_LIST(&server.atup_a,p,strbuf,len,addr_add_); break; case FILET: SCAN_STRING(p,strbuf,len); { char *errmsg; if (!read_resolv_conf(strbuf, &server.atup_a, &errmsg)) { if(errmsg) {REPORT_ERROR(errmsg); free(errmsg);} else *errstr=NULL; PARSERROR; } } break; case PORT: SCAN_UNSIGNED_NUM(server.port,p,"port option"); break; case SCHEME: SCAN_STRING(p,strbuf,len); STRNCP(server.scheme, strbuf,len, "scheme"); break; case UPTEST: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_PING || cnst==C_NONE || cnst==C_IF || cnst==C_EXEC || cnst==C_DEV || cnst==C_DIALD || cnst==C_QUERY,"bad qualifier in uptest= option."); server.uptest=cnst; } break; case TIMEOUT: SCAN_TIMESECS(server.timeout,p,"timeout option"); break; case PING_TIMEOUT: SCAN_UNSIGNED_NUM(server.ping_timeout,p,"ping_timeout option"); break; case PING_IP: SCAN_STRING(p,strbuf,len); { const char *err; if ((err=parse_ip(strbuf,&server.ping_a))) { REPORT_ERRORF("%s for the ping_ip= option.",err); PARSERROR; } } break; case UPTEST_CMD: SCAN_STRING(p,strbuf,len); STRNDUP(server.uptest_cmd, strbuf,len); SKIP_BLANKS(p); if(*p==',') { ++p; SKIP_BLANKS(p); SCAN_STRING(p,strbuf,len); STRNCP(server.uptest_usr, strbuf,len, "second argument of uptest_cmd"); } break; case QUERY_TEST_NAME: if(isalpha(*p)) { int cnst; SCAN_ALPHANUM(ps,p,len); if(*p!='.' && *p!='-') { cnst=lookup_const(ps,len); if(cnst==C_NONE) { if(server.query_test_name) free(server.query_test_name); server.query_test_name=NULL; break; } } p=ps; /* reset current char pointer and try again. */ } { unsigned char tname[DNSNAMEBUFSIZE], *copy; unsigned sz; SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,tname); sz=rhnlen(tname); copy= malloc(sz); if(!copy) { OUTOFMEMERROR; } memcpy(copy,tname,sz); if(server.query_test_name) free(server.query_test_name); server.query_test_name=copy; } break; case INTERVAL: if(isalpha(*p)) { int cnst; SCAN_ALPHANUM(ps,p,len); cnst=lookup_const(ps,len); if(cnst==C_ONQUERY) { server.interval=-1; } else if(cnst==C_ONTIMEOUT) { server.interval=-2; } else { goto bad_interval_option; } } else if(isdigit(*p)) { char *err; server.interval=strtotime(p,&p,&err); if(err) { REPORT_ERRORF("bad time specification in interval= option: %s",err); PARSERROR; } } else { bad_interval_option: REPORT_ERROR("bad qualifier in interval= option."); PARSERROR; } break; case INTERFACE: SCAN_STRING(p,strbuf,len); STRNCP(server.interface, strbuf,len, "interface"); break; case DEVICE: SCAN_STRING(p,strbuf,len); STRNCP(server.device, strbuf,len, "device"); break; case PURGE_CACHE: ASSIGN_ON_OFF(server.purge_cache,p,C_ON,"bad qualifier in purge_cache= option."); break; case CACHING: ASSIGN_ON_OFF(server.nocache,p,C_OFF,"bad qualifier in caching= option."); break; case LEAN_QUERY: ASSIGN_ON_OFF(server.lean_query,p,C_ON,"bad qualifier in lean_query= option."); break; case EDNS_QUERY: ASSIGN_ON_OFF(server.edns_query,p,C_ON,"bad qualifier in edns_query= option."); break; case PRESET: ASSIGN_ON_OFF(server.preset,p,C_ON,"bad qualifier in preset= option."); break; case PROXY_ONLY: ASSIGN_ON_OFF(server.is_proxy,p,C_ON,"bad qualifier in proxy_only= option."); break; case ROOT_SERVER: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_ON || cnst==C_OFF || cnst==C_DISCOVER,"bad qualifier in root_server= option."); server.rootserver= (cnst==C_DISCOVER? 2: cnst==C_ON); } break; case RANDOMIZE_SERVERS: ASSIGN_ON_OFF(server.rand_servers,p,C_ON,"bad qualifier in randomize_servers= option."); break; case POLICY: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_INCLUDED || cnst==C_EXCLUDED || cnst==C_SIMPLE_ONLY || cnst==C_FQDN_ONLY,"bad qualifier in policy= option."); server.policy=cnst; } break; case INCLUDE: SCAN_STRING_LIST(&server.alist,p,strbuf,len,include_list_add) break; case EXCLUDE: SCAN_STRING_LIST(&server.alist,p,strbuf,len,exclude_list_add) break; case REJECTLIST: SCAN_STRING_LIST(&server,p,strbuf,len,reject_add_); break; case REJECTPOLICY: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_FAIL || cnst==C_NEGATE,"bad qualifier in reject_policy= option."); server.rejectpolicy=cnst; } break; case REJECTRECURSIVELY: ASSIGN_ON_OFF(server.rejectrecursively,p,C_ON,"bad qualifier in reject_recursively= option."); break; case LABEL: SCAN_STRING(p,strbuf,len); STRNDUP(server.label,strbuf,len); break; default: /* we should never get here */ CLEANUP_GOTO(internal_parse_error); } /* end of switch(option) */ SKIP_BLANKS(p); if(*p!=';') CLEANUP_GOTO(expected_semicolon); ++p; SKIP_BLANKS(p); } if(*p!='}') CLEANUP_GOTO(expected_closing_brace); if (server.uptest==C_EXEC) { if (!server.uptest_cmd) { REPORT_ERROR("you must specify uptest_cmd if you specify uptest=exec!"); PARSERROR; } } if (server.is_proxy && server.rootserver) { REPORT_ERROR("A server may not be specified as both a proxy and a root-server."); PARSERROR; } if(server.rootserver && (server.policy==C_SIMPLE_ONLY || server.policy==C_FQDN_ONLY)) fprintf(stderr,"Warning: using policy=%s with a root-server usually makes no sense.", const_name(server.policy)); if (DA_NEL(server.atup_a)) { check_localaddrs(&server); if(!DA_NEL(server.atup_a)) { REPORT_ERROR("Server section contains only local IP addresses.\n" "Bind pdnsd to a different local IP address or specify different port numbers" " in global section and server section if you want pdnsd to query servers on" " the same machine."); PARSERROR; } } { int j,n=DA_NEL(server.atup_a); for(j=0;jis_up=server.preset; /* A negative test interval means don't test at startup or reconfig. */ if(server.interval<0) at->i_ts=time(NULL); } } if(server.interval==-1) global->onquery=1; if (!(*servers=DA_GROW1_F(*servers,(void(*)(void*))free_servparm))) { OUTOFMEMERROR; } DA_LAST(*servers)= server; # undef CLEANUP_HANDLER # define CLEANUP_HANDLER } break; case RR: { /* Initialize c_cent to all zeros. Then it should be safe to call free_cent() on it, even before calling init_cent(). */ dns_cent_t c_cent={0}; time_t c_ttl=86400; unsigned c_flags=DF_LOCAL; unsigned char reverse=0; # undef CLEANUP_HANDLER # define CLEANUP_HANDLER (free_cent(&c_cent DBG0)) while(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); option=lookup_keyword(ps,len,rr_options); if(!option) { REPORT_ERRORF("invalid option for rr section: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='=') CLEANUP_GOTO(expected_equals); ++p; SKIP_BLANKS(p); switch(option) { int tp; const char *tpname; case NAME: { unsigned char c_name[DNSNAMEBUFSIZE]; if (c_cent.qname) { REPORT_ERROR("You may specify only one name in a rr section."); PARSERROR; } SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,c_name); if (!init_cent(&c_cent, c_name, 0, 0, c_flags DBG0)) goto out_of_memory; } break; case TTL: SCAN_TIMESECS(c_ttl,p, "ttl option"); break; case AUTHREC: { int cnst; if (c_cent.qname) { REPORT_ERROR("The authrec= option has no effect unless it precedes name= in a rr section."); PARSERROR; } ASSIGN_CONST(cnst,p,cnst==C_ON || cnst==C_OFF,"Bad qualifier in authrec= option."); c_flags=(cnst==C_ON)?DF_LOCAL:0; } break; case REVERSE: ASSIGN_ON_OFF(reverse,p,C_ON,"bad qualifier in reverse= option."); break; case A: { unsigned int sz; pdnsd_ca c_a; if (!c_cent.qname) goto no_name_spec; SCAN_STRING(p,strbuf,len); if (inet_aton(strbuf,&c_a.ipv4)) { tp=T_A; sz=sizeof(struct in_addr); } else #if ALLOW_LOCAL_AAAA if (inet_pton(AF_INET6,strbuf,&c_a.ipv6)>0) { tp=T_AAAA; sz=sizeof(struct in6_addr); } else #endif { REPORT_ERROR("bad IP address in a= option."); PARSERROR; } if(!add_cent_rr(&c_cent,tp,c_ttl,0,CF_LOCAL,sz,&c_a DBG0)) goto add_rr_failed; } break; case OWNER: tp=T_NS; goto scan_name; case CNAME: tp=T_CNAME; goto scan_name; case PTR: tp=T_PTR; scan_name: { unsigned char c_name[DNSNAMEBUFSIZE]; if (!c_cent.qname) goto no_name_spec; SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,c_name); if(!add_cent_rr(&c_cent,tp,c_ttl,0,CF_LOCAL,rhnlen(c_name),c_name DBG0)) goto add_rr_failed; } break; case MX: { unsigned char *cp; unsigned pref; unsigned char c_mx[2+DNSNAMEBUFSIZE]; if (!c_cent.qname) goto no_name_spec; cp=c_mx+2; SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,cp); SKIP_COMMA(p,"missing second argument (preference level) of mx= option"); SCAN_UNSIGNED_NUM(pref,p,"second argument of mx= option"); cp=c_mx; PUTINT16(pref,cp); if(!add_cent_rr(&c_cent,T_MX,c_ttl,0,CF_LOCAL,2+rhnlen(cp),c_mx DBG0)) goto add_rr_failed; } break; case SOA: { unsigned int blen,rlen; unsigned char *bp; uint32_t val; unsigned char buf[2*DNSNAMEBUFSIZE+20]; if (!c_cent.qname) goto no_name_spec; SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,buf); rlen=rhnlen(buf); blen=rlen; bp=buf+rlen; SKIP_COMMA(p,"missing 2nd argument of soa= option"); SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,bp); rlen=rhnlen(bp); blen += rlen; bp += rlen; SKIP_COMMA(p,"missing 3rd argument of soa= option"); SCAN_UNSIGNED_NUM(val,p,"3rd argument of soa= option"); PUTINT32(val,bp); SKIP_COMMA(p,"missing 4th argument of soa= option"); SCAN_TIMESECS(val,p,"4th argument of soa= option"); PUTINT32(val,bp); SKIP_COMMA(p,"missing 5th argument of soa= option"); SCAN_TIMESECS(val,p,"5th argument of soa= option"); PUTINT32(val,bp); SKIP_COMMA(p,"missing 6th argument of soa= option"); SCAN_TIMESECS(val,p,"6th argument of soa= option"); PUTINT32(val,bp); SKIP_COMMA(p,"missing 7th argument of soa= option"); SCAN_TIMESECS(val,p,"7th argument of soa= option"); PUTINT32(val,bp); blen += 20; if(!add_cent_rr(&c_cent,T_SOA,c_ttl,0,CF_LOCAL,blen,buf DBG0)) goto add_rr_failed; } break; case SPF: #if IS_CACHED_SPF tp=T_SPF; tpname="spf"; goto define_txt_rr; #else REPORT_ERROR("Missing support for caching SPF records in rr section"); PARSERROR; #endif case TXT: #if IS_CACHED_TXT tp=T_TXT; tpname="txt"; #else REPORT_ERROR("Missing support for caching TXT records in rr section"); PARSERROR; #endif #if IS_CACHED_TXT || IS_CACHED_SPF #if IS_CACHED_SPF define_txt_rr: #endif { unsigned char *rbuf; unsigned sz,allocsz; int rv; if (!c_cent.qname) goto no_name_spec; rbuf=NULL; sz=allocsz=0; # undef CLEANUP_HANDLER2 # define CLEANUP_HANDLER2 (free(rbuf)) for(;;) { unsigned char *newbuf,*cp; unsigned newsz=sz+256; int n; if(newsz>allocsz) { allocsz += 512; newbuf=realloc(rbuf,allocsz); if(!newbuf) { OUTOFMEMERROR; } rbuf=newbuf; } cp = rbuf+sz; n=scan_string(&p, charp (cp+1), 255, &scanstrerr); if(n==-1) { REPORT_ERRORF("%s in %s= option", scanstrerr, tpname); PARSERROR; } if(n>255) { REPORT_ERRORF("string longer than 255 bytes in %s= option", tpname); PARSERROR; } *cp=n; sz += n+1; if(sz>0xffff) { REPORT_ERRORF("data exceeds maximum size (65535 bytes) in %s= option", tpname); PARSERROR; } SKIP_BLANKS(p); if(*p!=',') break; ++p; SKIP_BLANKS(p); } rv=add_cent_rr(&c_cent,tp,c_ttl,0,CF_LOCAL,sz,rbuf DBG0); CLEANUP_HANDLER2; # undef CLEANUP_HANDLER2 # define CLEANUP_HANDLER2 if(!rv) goto add_rr_failed; } break; #endif default: /* we should never get here */ CLEANUP_GOTO(internal_parse_error); } /* end of switch(option) */ SKIP_BLANKS(p); if(*p!=';') CLEANUP_GOTO(expected_semicolon); ++p; SKIP_BLANKS(p); } if(*p!='}') CLEANUP_GOTO(expected_closing_brace); if (!c_cent.qname) goto no_name_spec; if(c_cent.qname[0]==1 && c_cent.qname[1]=='*') { /* Wild card record. Set the DF_WILD flag for the name with '*.' removed. */ if(!set_cent_flags(&c_cent.qname[2],DF_WILD)) { unsigned char buf[DNSNAMEBUFSIZE]; rhn2str(c_cent.qname,buf,sizeof(buf)); REPORT_ERRORF("You must define some records for '%s'" " before you can define records for the wildcard name '%s'", &buf[2],buf); PARSERROR; } } add_cache(&c_cent); if(reverse) { if(!add_reverse_cache(&c_cent)) { REPORT_ERROR("Can't convert IP address in a= option" " into form suitable for reverse resolving."); PARSERROR; } } CLEANUP_HANDLER; break; add_rr_failed: OUTOFMEMERROR; # undef CLEANUP_HANDLER # define CLEANUP_HANDLER } case SOURCE: { unsigned char c_owner[DNSNAMEBUFSIZE]; time_t c_ttl; unsigned c_flags; unsigned char c_aliases; c_owner[0]='\0'; c_ttl=86400; c_flags=DF_LOCAL; c_aliases=0; while(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); option=lookup_keyword(ps,len,source_options); if(!option) { REPORT_ERRORF("invalid option for source section: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='=') goto expected_equals; ++p; SKIP_BLANKS(p); switch(option) { case OWNER: SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,c_owner); break; case TTL: SCAN_TIMESECS(c_ttl,p,"ttl option"); break; case FILET: if (!c_owner[0]) { REPORT_ERROR("you must specify owner before file= in source records."); PARSERROR; } SCAN_STRING(p,strbuf,len); { char *errmsg; if (!read_hosts(strbuf, c_owner, c_ttl, c_flags, c_aliases, &errmsg)) { if(errmsg) { REPORT_ERROR(errmsg); free(errmsg); } else *errstr=NULL; PARSERROR; } } break; case SERVE_ALIASES: ASSIGN_ON_OFF(c_aliases,p,C_ON,"Bad qualifier in serve_aliases= option."); break; case AUTHREC: { int cnst; ASSIGN_CONST(cnst,p,cnst==C_ON || cnst==C_OFF,"Bad qualifier in authrec= option."); c_flags=(cnst==C_ON)?DF_LOCAL:0; } break; default: /* we should never get here */ goto internal_parse_error; } /* end of switch(option) */ SKIP_BLANKS(p); if(*p!=';') goto expected_semicolon; ++p; SKIP_BLANKS(p); } } break; case INCLUDE_F: { while(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); option=lookup_keyword(ps,len,include_options); if(!option) { REPORT_ERRORF("invalid option for include section: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='=') goto expected_equals; ++p; SKIP_BLANKS(p); switch(option) { case FILET: if(includedepth>=MAXINCLUDEDEPTH) { REPORT_ERRORF("maximum include depth (%d) exceeded.",MAXINCLUDEDEPTH); PARSERROR; } SCAN_STRING(p,strbuf,len); { char *errmsg; if (!read_config_file(strbuf, NULL, NULL, includedepth+1, &errmsg)) { if(errmsg) { if(linenr) { if(asprintf(errstr, "In file %s included at line %u:\n%s",strbuf,linenr,errmsg)<0) *errstr=NULL; } else { if(asprintf(errstr, "In file %s:\n%s",strbuf,errmsg)<0) *errstr=NULL; } free(errmsg); } else *errstr=NULL; PARSERROR; } } break; default: /* we should never get here */ goto internal_parse_error; } /* end of switch(option) */ SKIP_BLANKS(p); if(*p!=';') goto expected_semicolon; ++p; SKIP_BLANKS(p); } } break; case NEG: { unsigned char c_name[DNSNAMEBUFSIZE]; time_t c_ttl; unsigned char htp,hdtp; htp=0; hdtp=0; c_name[0]='\0'; c_ttl=86400; while(isalpha(*p)) { SCAN_ALPHANUM(ps,p,len); option=lookup_keyword(ps,len,neg_options); if(!option) { REPORT_ERRORF("invalid option for neg section: %.*s",(int)len,ps); PARSERROR; } SKIP_BLANKS(p); if(*p!='=') goto expected_equals; ++p; SKIP_BLANKS(p); switch(option) { case NAME: SCAN_STRING(p,strbuf,len); PARSESTR2RHN(ucharp strbuf,len,c_name); break; case TTL: SCAN_TIMESECS(c_ttl,p, "ttl option"); break; case TYPES: if (!c_name[0]) { REPORT_ERROR("you must specify a name before the types= option."); PARSERROR; } if (isalpha(*p)) { int cnst; dns_cent_t c_cent /* ={0} */; SCAN_ALPHANUM(ps,p,len); cnst=lookup_const(ps,len); if(cnst==C_DOMAIN) { if (htp) { REPORT_ERROR("You may not specify types=domain together with other types!"); PARSERROR; } hdtp=1; if (!init_cent(&c_cent, c_name, c_ttl, 0, DF_LOCAL|DF_NEGATIVE DBG0)) goto out_of_memory; } else if(cnst==0) { if (hdtp) { REPORT_ERROR("You may not specify types=domain together with other types!"); PARSERROR; } htp=1; if (!init_cent(&c_cent, c_name, 0, 0, 0 DBG0)) goto out_of_memory; # undef CLEANUP_HANDLER # define CLEANUP_HANDLER (free_cent(&c_cent DBG0)) for(;;) { { TEMPSTRNCPY(buf,ps,len); cnst=rr_tp_byname(buf); } if(cnst==-1) { REPORT_ERRORF("unrecognized rr type '%.*s' used as argument for types= option.",(int)len,ps); PARSERROR; } if(PDNSD_NOT_CACHED_TYPE(cnst)) { REPORT_ERRORF("illegal rr type '%.*s' used as argument for types= option.",(int)len,ps); PARSERROR; } if (!getrrset_eff(&c_cent,cnst) && !add_cent_rrset_by_type(&c_cent,cnst,c_ttl,0,CF_LOCAL|CF_NEGATIVE DBG0)) { OUTOFMEMERROR; } SKIP_BLANKS(p); if(*p!=',') break; ++p; SKIP_BLANKS(p); if (!isalpha(*p)) {CLEANUP_GOTO(bad_types_option);} SCAN_ALPHANUM(ps,p,len); } } else goto bad_types_option; add_cache(&c_cent); CLEANUP_HANDLER; # undef CLEANUP_HANDLER # define CLEANUP_HANDLER } else { bad_types_option: REPORT_ERROR("Bad argument for types= option."); PARSERROR; } break; default: /* we should never get here */ goto internal_parse_error; } /* end of switch(option) */ SKIP_BLANKS(p); if(*p!=';') goto expected_semicolon; ++p; SKIP_BLANKS(p); } } break; default: /* we should never get here */ goto internal_parse_error; } /* end of switch(sechdr) */ if(*p!='}') goto expected_closing_brace; ++p; } else { REPORT_ERROR("expected section header"); PARSERROR; } } if(!in || feof(in)) { if(getnextperr) { REPORT_ERROR(getnextperr); PARSERROR; } retval=1; /* success */ } else goto input_error; goto free_linebuf_return; expected_bropen: REPORT_ERROR("expected opening brace after section name"); PARSERROR; expected_closing_brace: REPORT_ERROR("expected beginning of new option or closing brace"); PARSERROR; expected_equals: REPORT_ERROR("expected equals sign after option name"); PARSERROR; expected_semicolon: REPORT_ERROR("too many arguments to option or missing semicolon"); PARSERROR; string_err: REPORT_ERROR(scanstrerr); PARSERROR; string_too_long: REPORT_ERROR("string length exceeds buffer size"); PARSERROR; no_name_spec: REPORT_ERROR("you must specify a name before a,ptr,cname,mx,ns(owner) and soa records."); PARSERROR; internal_parse_error: if(asprintf(errstr,"Internal inconsistency detected while parsing line %u of %s.\n" "Please consider reporting this error to one of the maintainers.\n",linenr,conftype)<0) *errstr=NULL; PARSERROR; out_of_memory: /* If malloc() just failed, allocating space for an error message is unlikely to succeed. */ *errstr=NULL; PARSERROR; unexpected_eof: if(!in || feof(in)) { REPORT_ERROR(getnextperr?getnextperr:in?"unexpected end of file":"unexpected end of input string"); } else input_error: { if(asprintf(errstr,"Error while reading config file: %s",strerror(errno))<0) *errstr=NULL; } free_linebuf_return: free(linebuf); return retval; #undef SKIP_BLANKS #undef SCAN_STRING #undef REPORT_ERROR #undef REPORT_ERRORF #undef PARSERROR #undef OUTOFMEMERROR #undef CLEANUP_GOTO } /* Convert a string representation of an IP address into a binary format. */ static const char* parse_ip(const char *ipstr, pdnsd_a *a) { #if defined(ENABLE_IPV4) && defined(ENABLE_IPV6) if(!cmdlineipv) cmdlineipv=-2; #endif { if(!strcmp(ipstr,"any")) { #ifdef ENABLE_IPV4 if (run_ipv4) a->ipv4.s_addr=INADDR_ANY; #endif #ifdef ENABLE_IPV6 ELSE_IPV6 a->ipv6=in6addr_any; #endif } else if(!str2pdnsd_a(ipstr,a)) { #if defined(ENABLE_IPV4) && defined(ENABLE_IPV6) if(run_ipv4 && inet_pton(AF_INET6,ipstr,&a->ipv6)>0) { return "You should set run_ipv4=off or use the command-line option -6" " before specifying an IPv6 address"; } #endif return "bad IP address"; } } return NULL; } /* Add an IP address to the list of name servers. */ static const char *addr_add(atup_array *ata, const char *ipstr) { atup_t *at; pdnsd_a addr; #if defined(ENABLE_IPV4) && defined(ENABLE_IPV6) if(!cmdlineipv) cmdlineipv=-2; #endif { if(!str2pdnsd_a(ipstr,&addr)) { #if defined(ENABLE_IPV4) && defined(ENABLE_IPV6) if(run_ipv4 && inet_pton(AF_INET6,ipstr,&addr.ipv6)>0) { fprintf(stderr,"IPv6 address \"%s\" in config file ignored while running in IPv4 mode.\n",ipstr); return NULL; } #endif return "bad IP address"; } } if (!(*ata=DA_GROW1(*ata))) { return "out of memory!"; } at=&DA_LAST(*ata); SET_PDNSD_A2(&at->a, &addr); at->is_up=0; at->i_ts=0; return NULL; } /* Helper functions for making netmasks */ inline static uint32_t mk_netmask4(int len) { uint32_t m; if(len<=0) return 0; m= ~(uint32_t)0; return (len<32)? htonl(m<<(32-len)): m; } #if ALLOW_LOCAL_AAAA inline static void mk_netmask6(struct in6_addr *m, int len) { uint32_t *ma = (uint32_t *)m; ma[0] = mk_netmask4(len); ma[1] = mk_netmask4(len -= 32); ma[2] = mk_netmask4(len -= 32); ma[3] = mk_netmask4(len -= 32); } #endif /* Add an IP address/mask to the reject lists. */ static const char *reject_add(servparm_t *serv, const char *ipstr) { char *slash=strchr(ipstr,'/'); int mlen=0; if(slash) { *slash++=0; if(*slash && isdigit(*slash)) { char *endptr; int l = strtol(slash,&endptr,10); if(!*endptr) { mlen=l; slash=NULL; } } } else mlen=128; /* Works for both IPv4 and IPv6 */ { addr4maskpair_t am; am.mask.s_addr = mk_netmask4(mlen); if(inet_aton(ipstr,&am.a) && (!slash || inet_aton(slash,&am.mask))) { if(!(serv->reject_a4=DA_GROW1(serv->reject_a4))) return "out of memory!"; DA_LAST(serv->reject_a4) = am; return NULL; } } #if ALLOW_LOCAL_AAAA { addr6maskpair_t am; mk_netmask6(&am.mask,mlen); if(inet_pton(AF_INET6,ipstr,&am.a)>0 && (!slash || inet_pton(AF_INET6,slash,&am.mask)>0)) { if(!(serv->reject_a6=DA_GROW1(serv->reject_a6))) return "out of memory!"; DA_LAST(serv->reject_a6) = am; return NULL; } } #endif return "bad IP address"; } /* Try to avoid the possibility that pdnsd will query itself. */ static void check_localaddrs(servparm_t *serv) { if(serv->port == global.port) { atup_array ata=serv->atup_a; int i,j=0,n=DA_NEL(ata); for(i=0;ia))) { char buf[ADDRSTR_MAXLEN]; fprintf(stderr,"Local name-server address \"%s\" ignored in config file.\n", pdnsd_a2str(PDNSD_A2_TO_A(&at->a),buf,ADDRSTR_MAXLEN)); continue; } } else { if(equiv_inaddr2(&global.a,&at->a)) { char buf[ADDRSTR_MAXLEN]; fprintf(stderr,"Ignoring name-server address \"%s\" in config file (identical to server_ip address).\n", pdnsd_a2str(PDNSD_A2_TO_A(&at->a),buf,ADDRSTR_MAXLEN)); continue; } } if(jatup_a=DA_RESIZE(ata,j); } } /* Read the name server addresses from a resolv.conf-style file. */ static int read_resolv_conf(const char *fn, atup_array *ata, char **errstr) { int rv=0; FILE *f; char *buf; size_t buflen=256; unsigned linenr=0; if (!(f=fopen(fn,"r"))) { if(asprintf(errstr, "Failed to open %s: %s", fn, strerror(errno))<0) *errstr=NULL; return 0; } buf=malloc(buflen); if(!buf) { *errstr=NULL; goto fclose_return; } while(getline(&buf,&buflen,f)>=0) { size_t len; char *p,*ps; ++linenr; p=buf; for(;; ++p) { if(!*p) goto nextline; if(!isspace(*p)) break; } ps=p; do { if(!*++p) goto nextline; } while(!isspace(*p)); len=p-ps; if(len==strlitlen("nameserver") && !strncmp(ps,"nameserver",len)) { const char *errmsg; do { if(!*++p) goto nextline; } while (isspace(*p)); ps=p; do { ++p; } while(*p && !isspace(*p)); len=p-ps; { TEMPSTRNCPY(ipstr,ps,len); errmsg=addr_add(ata, ipstr); } if(errmsg) { if(asprintf(errstr, "%s in line %u of file %s", errmsg,linenr,fn)<0) *errstr=NULL; goto cleanup_return; } } nextline:; } if (feof(f)) rv=1; else if(asprintf(errstr, "Failed to read %s: %s", fn, strerror(errno))<0) *errstr=NULL; cleanup_return: free(buf); fclose_return: fclose(f); return rv; } static const char *slist_add(slist_array *sla, const char *nm, unsigned int len, int tp) { slist_t *sl; int exact=1; const char *err; size_t sz; unsigned char rhn[DNSNAMEBUFSIZE]; if (len>1 && *nm=='.') { exact=0; ++nm; --len; } if((err=parsestr2rhn(ucharp nm,len,rhn))) return err; sz=rhnlen(rhn); if (!(*sla=DA_GROW1_F(*sla,free_slist_domain))) { return "out of memory!"; } sl=&DA_LAST(*sla); sl->exact=exact; sl->rule=tp; if (!(sl->domain=malloc(sz))) return "out of memory!"; memcpy(sl->domain,rhn,sz); return NULL; } static const char *zone_add(zone_array *za, const char *zone, unsigned int len) { zone_t z; const char *err; size_t sz; unsigned char rhn[DNSNAMEBUFSIZE]; if((err=parsestr2rhn(ucharp zone,len,rhn))) return err; sz=rhnlen(rhn); if(!(*za=DA_GROW1_F(*za,free_zone)) || !(DA_LAST(*za)=z=malloc(sz))) return "out of memory!"; memcpy(z,rhn,sz); return NULL; }