blob: d0ac22b2c4c6994e56e77c6e07f52483650f514a [file] [log] [blame]
/* dnsmasq is Copyright (c) 2000 - 2005 Simon Kelley
This program 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; version 2 dated June, 1991.
This program 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.
*/
/* Author's email: simon@thekelleys.org.uk */
#include "dnsmasq.h"
struct myoption {
const char *name;
int has_arg;
int *flag;
int val;
};
#define OPTSTRING "31yZDNLERKzowefnbvhdkqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:j:P:J:W:Y:2:"
static const struct myoption opts[] = {
{"version", 0, 0, 'v'},
{"no-hosts", 0, 0, 'h'},
{"no-poll", 0, 0, 'n'},
{"help", 0, 0, 'w'},
{"no-daemon", 0, 0, 'd'},
{"log-queries", 0, 0, 'q'},
{"user", 1, 0, 'u'},
{"group", 1, 0, 'g'},
{"resolv-file", 1, 0, 'r'},
{"mx-host", 1, 0, 'm'},
{"mx-target", 1, 0, 't'},
{"cache-size", 1, 0, 'c'},
{"port", 1, 0, 'p'},
{"dhcp-leasefile", 1, 0, 'l'},
{"dhcp-lease", 1, 0, 'l' },
{"dhcp-host", 1, 0, 'G'},
{"dhcp-range", 1, 0, 'F'},
{"dhcp-option", 1, 0, 'O'},
{"dhcp-boot", 1, 0, 'M'},
{"domain", 1, 0, 's'},
{"domain-suffix", 1, 0, 's'},
{"interface", 1, 0, 'i'},
{"listen-address", 1, 0, 'a'},
{"bogus-priv", 0, 0, 'b'},
{"bogus-nxdomain", 1, 0, 'B'},
{"selfmx", 0, 0, 'e'},
{"filterwin2k", 0, 0, 'f'},
{"pid-file", 1, 0, 'x'},
{"strict-order", 0, 0, 'o'},
{"server", 1, 0, 'S'},
{"local", 1, 0, 'S' },
{"address", 1, 0, 'A' },
{"conf-file", 1, 0, 'C'},
{"no-resolv", 0, 0, 'R'},
{"expand-hosts", 0, 0, 'E'},
{"localmx", 0, 0, 'L'},
{"local-ttl", 1, 0, 'T'},
{"no-negcache", 0, 0, 'N'},
{"addn-hosts", 1, 0, 'H'},
{"query-port", 1, 0, 'Q'},
{"except-interface", 1, 0, 'I'},
{"no-dhcp-interface", 1, 0, '2'},
{"domain-needed", 0, 0, 'D'},
{"dhcp-lease-max", 1, 0, 'X' },
{"bind-interfaces", 0, 0, 'z'},
{"read-ethers", 0, 0, 'Z' },
{"alias", 1, 0, 'V' },
{"dhcp-vendorclass", 1, 0, 'U'},
{"dhcp-userclass", 1, 0, 'j'},
{"dhcp-ignore", 1, 0, 'J'},
{"edns-packet-max", 1, 0, 'P'},
{"keep-in-foreground", 0, 0, 'k'},
{"dhcp-authoritative", 0, 0, 'K'},
{"srv-host", 1, 0, 'W'},
{"localise-queries", 0, 0, 'y'},
{"txt-record", 1, 0, 'Y'},
{"enable-dbus", 0, 0, '1'},
{"bootp-dynamic", 0, 0, '3'},
{0, 0, 0, 0}
};
struct optflags {
char c;
unsigned int flag;
};
static const struct optflags optmap[] = {
{ 'b', OPT_BOGUSPRIV },
{ 'f', OPT_FILTER },
{ 'q', OPT_LOG },
{ 'e', OPT_SELFMX },
{ 'h', OPT_NO_HOSTS },
{ 'n', OPT_NO_POLL },
{ 'd', OPT_DEBUG },
{ 'k', OPT_NO_FORK },
{ 'K', OPT_AUTHORITATIVE },
{ 'o', OPT_ORDER },
{ 'R', OPT_NO_RESOLV },
{ 'E', OPT_EXPAND },
{ 'L', OPT_LOCALMX },
{ 'N', OPT_NO_NEG },
{ 'D', OPT_NODOTS_LOCAL },
{ 'z', OPT_NOWILD },
{ 'Z', OPT_ETHERS },
{ 'y', OPT_LOCALISE },
{ '1', OPT_DBUS },
{ '3', OPT_BOOTP_DYNAMIC },
{ 'v', 0},
{ 'w', 0},
{ 0, 0 }
};
static const char * const usage =
"Usage: dnsmasq [options]\n\n"
#ifndef HAVE_GETOPT_LONG
"Use short options only on the command line.\n"
#endif
"Valid options are :\n"
"-a, --listen-address=ipaddr Specify local address(es) to listen on.\n"
"-A, --address=/domain/ipaddr Return ipaddr for all hosts in specified domains.\n"
"-b, --bogus-priv Fake reverse lookups for RFC1918 private address ranges.\n"
"-B, --bogus-nxdomain=ipaddr Treat ipaddr as NXDOMAIN (defeats Verisign wildcard).\n"
"-c, --cache-size=cachesize Specify the size of the cache in entries (defaults to %d).\n"
"-C, --conf-file=path Specify configuration file (defaults to " CONFFILE ").\n"
"-d, --no-daemon Do NOT fork into the background: run in debug mode.\n"
"-D, --domain-needed Do NOT forward queries with no domain part.\n"
"-e, --selfmx Return self-pointing MX records for local hosts.\n"
"-E, --expand-hosts Expand simple names in /etc/hosts with domain-suffix.\n"
"-f, --filterwin2k Don't forward spurious DNS requests from Windows hosts.\n"
"-F, --dhcp-range=ipaddr,ipaddr,time Enable DHCP in the range given with lease duration.\n"
"-g, --group=groupname Change to this group after startup (defaults to " CHGRP ").\n"
"-G, --dhcp-host=<hostspec> Set address or hostname for a specified machine.\n"
"-h, --no-hosts Do NOT load " HOSTSFILE " file.\n"
"-H, --addn-hosts=path Specify a hosts file to be read in addition to " HOSTSFILE ".\n"
"-i, --interface=interface Specify interface(s) to listen on.\n"
"-I, --except-interface=int Specify interface(s) NOT to listen on.\n"
"-j, --dhcp-userclass=<id>,<class> Map DHCP user class to option set.\n"
"-J, --dhcp-ignore=<id> Don't do DHCP for hosts in option set.\n"
"-k, --keep-in-foreground Do NOT fork into the background, do NOT run in debug mode.\n"
"-K, --dhcp-authoritative Assume we are the only DHCP server on the local network.\n"
"-l, --dhcp-leasefile=path Specify where to store DHCP leases (defaults to " LEASEFILE ").\n"
"-L, --localmx Return MX records for local hosts.\n"
"-m, --mx-host=host_name,target,pref Specify an MX record.\n"
"-M, --dhcp-boot=<bootp opts> Specify BOOTP options to DHCP server.\n"
"-n, --no-poll Do NOT poll " RESOLVFILE " file, reload only on SIGHUP.\n"
"-N, --no-negcache Do NOT cache failed search results.\n"
"-o, --strict-order Use nameservers strictly in the order given in " RESOLVFILE ".\n"
"-O, --dhcp-option=<optspec> Set extra options to be set to DHCP clients.\n"
"-p, --port=number Specify port to listen for DNS requests on (defaults to 53).\n"
"-P, --edns-packet-max=<size> Maximum supported UDP packet size for EDNS.0 (defaults to %d).\n"
"-q, --log-queries Log queries.\n"
"-Q, --query-port=number Force the originating port for upstream queries.\n"
"-R, --no-resolv Do NOT read resolv.conf.\n"
"-r, --resolv-file=path Specify path to resolv.conf (defaults to " RESOLVFILE ").\n"
"-S, --server=/domain/ipaddr Specify address(es) of upstream servers with optional domains.\n"
" --local=/domain/ Never forward queries to specified domains.\n"
"-s, --domain=domain Specify the domain to be assigned in DHCP leases.\n"
"-t, --mx-target=host_name Specify default target in an MX record.\n"
"-T, --local-ttl=time Specify time-to-live in seconds for replies from /etc/hosts.\n"
"-u, --user=username Change to this user after startup. (defaults to " CHUSER ").\n"
"-U, --dhcp-vendorclass=<id>,<class> Map DHCP vendor class to option set.\n"
"-v, --version Display dnsmasq version and copyright information.\n"
"-V, --alias=addr,addr,mask Translate IPv4 addresses from upstream servers.\n"
"-W, --srv-host=name,target,... Specify a SRV record.\n"
"-w, --help Display this message.\n"
"-x, --pid-file=path Specify path of PID file. (defaults to " RUNFILE ").\n"
"-X, --dhcp-lease-max=number Specify maximum number of DHCP leases (defaults to %d).\n"
"-y, --localise-queries Answer DNS queries based on the interface a query was sent to.\n"
"-Y --txt-record=name,txt.... Specify TXT DNS record.\n"
"-z, --bind-interfaces Bind only to interfaces in use.\n"
"-Z, --read-ethers Read DHCP static host information from " ETHERSFILE ".\n"
"-1, --enable-dbus Enable the DBus interface for setting upstream servers, etc.\n"
"-2, --no-dhcp-interface=interface Do not provide DHCP on this interface, only provide DNS.\n"
"-3, --bootp-dynamic Enable dynamic address allocation for bootp.\n"
"\n";
/* We hide metacharaters in quoted strings by mapping them into the ASCII control
character space. Note that the \0, \t \a \b \r and \n characters are carefully placed in the
following sequence so that they map to themselves: it is therefore possible to call
unhide_metas repeatedly on string without breaking things.
The transformation gets undone by opt_canonicalise, atoi_check and safe_string_alloc, and a
couple of other places. */
static char meta[] = "\000123456\a\b\t\n78\r90abcdefABCDEF:,.";
static char hide_meta(char c)
{
unsigned int i;
for (i = 0; i < (sizeof(meta) - 1); i++)
if (c == meta[i])
return (char)i;
return c;
}
static char unhide_meta(char cr)
{
unsigned int c = cr;
if (c < (sizeof(meta) - 1))
cr = meta[c];
return cr;
}
static void unhide_metas(char *cp)
{
if (cp)
for(; *cp; cp++)
*cp = unhide_meta(*cp);
}
static char *safe_string_alloc(char *cp)
{
char *ret = NULL;
if (cp && strlen(cp) != 0)
{
ret = safe_malloc(strlen(cp)+1);
strcpy(ret, cp);
/* restore hidden metachars */
unhide_metas(ret);
}
return ret;
}
static char *safe_strchr(char *s, int c)
{
if (!s)
return NULL;
return strchr(s, c);
}
static int canonicalise_opt(char *s)
{
if (!s)
return 0;
unhide_metas(s);
return canonicalise(s);
}
static int atoi_check(char *a, int *res)
{
char *p;
if (!a)
return 0;
unhide_metas(a);
for (p = a; *p; p++)
if (*p < '0' || *p > '9')
return 0;
*res = atoi(a);
return 1;
}
static void add_txt(struct daemon *daemon, char *name, char *txt)
{
size_t len = strlen(txt);
struct txt_record *r = safe_malloc(sizeof(struct txt_record));
r->name = safe_string_alloc(name);
r->next = daemon->txt;
daemon->txt = r;
r->class = C_CHAOS;
r->txt = safe_malloc(len+1);
r->len = len+1;
*(r->txt) = len;
memcpy((r->txt)+1, txt, len);
}
struct daemon *read_opts (int argc, char **argv, char *compile_opts)
{
struct daemon *daemon = safe_malloc(sizeof(struct daemon));
char *problem = NULL, *buff = safe_malloc(MAXDNAME);
int option = 0, i;
FILE *file_save = NULL, *f = NULL;
char *p, *arg, *comma, *file_name_save = NULL, *conffile = CONFFILE;
int hosts_index = 1, conffile_set = 0;
int line_save = 0, lineno = 0;
opterr = 0;
memset(daemon, 0, sizeof(struct daemon));
daemon->namebuff = buff;
/* Set defaults - everything else is zero or NULL */
daemon->min_leasetime = UINT_MAX;
daemon->cachesize = CACHESIZ;
daemon->port = NAMESERVER_PORT;
daemon->default_resolv.is_default = 1;
daemon->default_resolv.name = RESOLVFILE;
daemon->resolv_files = &daemon->default_resolv;
daemon->username = CHUSER;
daemon->groupname = CHGRP;
daemon->runfile = RUNFILE;
daemon->dhcp_max = MAXLEASES;
daemon->edns_pktsz = EDNS_PKTSZ;
add_txt(daemon, "version.bind", "dnsmasq-" VERSION );
add_txt(daemon, "authors.bind", "Simon Kelley");
add_txt(daemon, "copyright.bind", COPYRIGHT);
while (1)
{
problem = NULL;
if (!f)
{
#ifdef HAVE_GETOPT_LONG
option = getopt_long(argc, argv, OPTSTRING, (struct option *)opts, NULL);
#else
option = getopt(argc, argv, OPTSTRING);
#endif
/* Copy optarg so that argv doesn't get changed */
if (optarg)
{
strncpy(buff, optarg, MAXDNAME);
buff[MAXDNAME-1] = 0;
arg = buff;
}
else
arg = NULL;
}
else
{ /* f non-NULL, reading from conffile. */
reread:
if (!fgets(buff, MAXDNAME, f))
{
/* At end of file, all done */
fclose(f);
if (file_save)
{
/* may be nested */
conffile = file_name_save;
f = file_save;
file_save = NULL;
lineno = line_save;
goto reread;
}
break;
}
else
{
int white;
unsigned int lastquote;
lineno++;
/* Implement quotes, inside quotes we allow \\ \" \n and \t
metacharacters get hidden also strip comments */
for (white = 1, lastquote = 0, p = buff; *p; p++)
{
if (*p == '"')
{
memmove(p, p+1, strlen(p+1)+1);
for(; *p && *p != '"'; p++)
{
if (*p == '\\' && strchr("\"tnabr\\", p[1]))
{
if (p[1] == 't')
p[1] = '\t';
else if (p[1] == 'n')
p[1] = '\n';
else if (p[1] == 'a')
p[1] = '\a';
else if (p[1] == 'b')
p[1] = '\b';
else if (p[1] == 'r')
p[1] = '\r';
memmove(p, p+1, strlen(p+1)+1);
}
*p = hide_meta(*p);
}
if (*p == '"')
{
memmove(p, p+1, strlen(p+1)+1);
lastquote = p - buff;
}
else
complain("missing \"", lineno, conffile);
}
if (white && *p == '#')
{
*p = 0;
break;
}
white = isspace(unhide_meta(*p));
}
/* fgets gets end of line char too. */
while (strlen(buff) > lastquote && isspace(unhide_meta(buff[strlen(buff)-1])))
buff[strlen(buff)-1] = 0;
if (*buff == 0)
continue;
if ((p=strchr(buff, '=')))
{
arg = p+1;
*p = 0;
}
else
arg = NULL;
option = 0;
for (i=0; opts[i].name; i++)
if (strcmp(opts[i].name, buff) == 0)
option = opts[i].val;
if (!option)
{
complain("bad option", lineno, conffile);
continue;
}
}
}
if (option == -1)
{ /* end of command line args, start reading conffile. */
if (!conffile)
break; /* "confile=" option disables */
fileopen:
option = 0;
if (!(f = fopen(conffile, "r")))
{
if (errno == ENOENT && !conffile_set)
break; /* No conffile, all done. */
else
die("cannot read %s: %s", conffile);
}
}
if (!f && option == 'w')
{
printf (usage, CACHESIZ, EDNS_PKTSZ, MAXLEASES);
exit(0);
}
if (!f && option == 'v')
{
printf("Dnsmasq version %s %s\n", VERSION, COPYRIGHT);
printf("Compile time options %s\n\n", compile_opts);
printf("This software comes with ABSOLUTELY NO WARRANTY.\n");
printf("Dnsmasq is free software, and you are welcome to redistribute it\n");
printf("under the terms of the GNU General Public License, version 2.\n");
exit(0);
}
for (i=0; optmap[i].c; i++)
if (option == optmap[i].c)
{
daemon->options |= optmap[i].flag;
option = 0;
if (f && arg)
complain("extraneous parameter", lineno, conffile);
break;
}
if (option && option != '?')
{
if (f && !arg)
{
complain("missing parameter", lineno, conffile);
continue;
}
switch (option)
{
case 'C':
if (!f)
{
conffile = safe_string_alloc(arg);
conffile_set = 1;
break;
}
/* nest conffiles one deep */
if (file_save)
{
complain("nested includes not allowed", lineno, conffile);
continue;
}
file_name_save = conffile;
file_save = f;
line_save = lineno;
conffile = safe_string_alloc(arg);
conffile_set = 1;
lineno = 0;
goto fileopen;
case 'x':
daemon->runfile = safe_string_alloc(arg);
break;
case 'r':
{
char *name = safe_string_alloc(arg);
struct resolvc *new, *list = daemon->resolv_files;
if (list && list->is_default)
{
/* replace default resolv file - possibly with nothing */
if (name)
{
list->is_default = 0;
list->name = name;
}
else
list = NULL;
}
else if (name)
{
new = safe_malloc(sizeof(struct resolvc));
new->next = list;
new->name = name;
new->is_default = 0;
new->mtime = 0;
new->logged = 0;
list = new;
}
daemon->resolv_files = list;
break;
}
case 'm':
{
int pref = 1;
struct mx_srv_record *new;
if ((comma = safe_strchr(arg, ',')))
{
char *prefstr;
*(comma++) = 0;
if ((prefstr=strchr(comma, ',')))
{
*(prefstr++) = 0;
if (!atoi_check(prefstr, &pref))
{
option = '?';
problem = "bad MX preference";
break;
}
}
}
if (!canonicalise_opt(arg) || (comma && !canonicalise_opt(comma)))
{
option = '?';
problem = "bad MX name";
break;
}
new = safe_malloc(sizeof(struct mx_srv_record));
new->next = daemon->mxnames;
daemon->mxnames = new;
new->issrv = 0;
new->name = safe_string_alloc(arg);
new->target = safe_string_alloc(comma); /* may be NULL */
new->weight = pref;
break;
}
case 't':
if (!canonicalise_opt(arg))
{
option = '?';
problem = "bad MX target";
}
else
daemon->mxtarget = safe_string_alloc(arg);
break;
case 'l':
daemon->lease_file = safe_string_alloc(arg);
break;
case 'H':
{
struct hostsfile *new = safe_malloc(sizeof(struct hostsfile));
new->fname = safe_string_alloc(arg);
new->index = hosts_index++;
new->next = daemon->addn_hosts;
daemon->addn_hosts = new;
break;
}
case 's':
if (strcmp (arg, "#") == 0)
daemon->options |= OPT_RESOLV_DOMAIN;
else if (!canonicalise_opt(arg))
option = '?';
else
daemon->domain_suffix = safe_string_alloc(arg);
break;
case 'u':
daemon->username = safe_string_alloc(arg);
break;
case 'g':
daemon->groupname = safe_string_alloc(arg);
break;
case 'i':
do {
struct iname *new = safe_malloc(sizeof(struct iname));
if ((comma = safe_strchr(arg, ',')))
*comma++ = 0;
new->next = daemon->if_names;
daemon->if_names = new;
/* new->name may be NULL if someone does
"interface=" to disable all interfaces except loop. */
new->name = safe_string_alloc(arg);
new->isloop = new->used = 0;
if (safe_strchr(new->name, ':'))
daemon->options |= OPT_NOWILD;
arg = comma;
} while (arg);
break;
case 'I':
case '2':
do {
struct iname *new = safe_malloc(sizeof(struct iname));
if ((comma = safe_strchr(arg, ',')))
*comma++ = 0;
new->name = safe_string_alloc(arg);
if (option == 'I')
{
new->next = daemon->if_except;
daemon->if_except = new;
if (safe_strchr(new->name, ':'))
daemon->options |= OPT_NOWILD;
}
else
{
new->next = daemon->dhcp_except;
daemon->dhcp_except = new;
}
arg = comma;
} while (arg);
break;
case 'B':
{
struct in_addr addr;
unhide_metas(arg);
if (arg && (addr.s_addr = inet_addr(arg)) != (in_addr_t)-1)
{
struct bogus_addr *baddr = safe_malloc(sizeof(struct bogus_addr));
baddr->next = daemon->bogus_addr;
daemon->bogus_addr = baddr;
baddr->addr = addr;
}
else
option = '?'; /* error */
break;
}
case 'a':
do {
struct iname *new = safe_malloc(sizeof(struct iname));
if ((comma = safe_strchr(arg, ',')))
*comma++ = 0;
unhide_metas(arg);
new->next = daemon->if_addrs;
if (arg && (new->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t)-1)
{
new->addr.sa.sa_family = AF_INET;
#ifdef HAVE_SOCKADDR_SA_LEN
new->addr.in.sin_len = sizeof(struct sockaddr_in);
#endif
}
#ifdef HAVE_IPV6
else if (arg && inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0)
{
new->addr.sa.sa_family = AF_INET6;
new->addr.in6.sin6_flowinfo = htonl(0);
#ifdef HAVE_SOCKADDR_SA_LEN
new->addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif
}
#endif
else
{
option = '?'; /* error */
free(new);
break;
}
daemon->if_addrs = new;
arg = comma;
} while (arg);
break;
case 'S':
case 'A':
{
struct server *serv, *newlist = NULL;
unhide_metas(arg);
if (arg && *arg == '/')
{
char *end;
arg++;
while ((end = strchr(arg, '/')))
{
char *domain = NULL;
*end = 0;
/* # matches everything and becomes a zero length domain string */
if (strcmp(arg, "#") == 0)
domain = "";
else if (!canonicalise_opt(arg) && strlen(arg) != 0)
option = '?';
else
domain = safe_string_alloc(arg); /* NULL if strlen is zero */
serv = safe_malloc(sizeof(struct server));
serv->next = newlist;
newlist = serv;
serv->sfd = NULL;
serv->domain = domain;
serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS;
arg = end+1;
}
if (!newlist)
{
option = '?';
break;
}
}
else
{
newlist = safe_malloc(sizeof(struct server));
newlist->next = NULL;
newlist->flags = 0;
newlist->sfd = NULL;
newlist->domain = NULL;
}
if (option == 'A')
{
newlist->flags |= SERV_LITERAL_ADDRESS;
if (!(newlist->flags & SERV_TYPE))
option = '?';
}
if (!arg || !*arg)
{
newlist->flags |= SERV_NO_ADDR; /* no server */
if (newlist->flags & SERV_LITERAL_ADDRESS)
option = '?';
}
else
{
int source_port = 0, serv_port = NAMESERVER_PORT;
char *portno, *source;
if ((source = strchr(arg, '@'))) /* is there a source. */
{
*source = 0;
if ((portno = strchr(source+1, '#')))
{
*portno = 0;
if (!atoi_check(portno+1, &source_port))
{
option = '?';
problem = "bad port";
}
}
}
if ((portno = strchr(arg, '#'))) /* is there a port no. */
{
*portno = 0;
if (!atoi_check(portno+1, &serv_port))
{
option = '?';
problem = "bad port";
}
}
if ((newlist->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t) -1)
{
newlist->addr.in.sin_port = htons(serv_port);
newlist->source_addr.in.sin_port = htons(source_port);
newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET;
#ifdef HAVE_SOCKADDR_SA_LEN
newlist->source_addr.in.sin_len = newlist->addr.in.sin_len = sizeof(struct sockaddr_in);
#endif
if (source)
{
if ((newlist->source_addr.in.sin_addr.s_addr = inet_addr(source+1)) != (in_addr_t) -1)
newlist->flags |= SERV_HAS_SOURCE;
else
option = '?'; /* error */
}
else
newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY;
}
#ifdef HAVE_IPV6
else if (inet_pton(AF_INET6, arg, &newlist->addr.in6.sin6_addr) > 0)
{
newlist->addr.in6.sin6_port = htons(serv_port);
newlist->source_addr.in6.sin6_port = htons(source_port);
newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET6;
newlist->addr.in6.sin6_flowinfo = newlist->source_addr.in6.sin6_flowinfo = htonl(0);
#ifdef HAVE_SOCKADDR_SA_LEN
newlist->addr.in6.sin6_len = newlist->source_addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif
if (source)
{
if (inet_pton(AF_INET6, source+1, &newlist->source_addr.in6.sin6_addr) > 0)
newlist->flags |= SERV_HAS_SOURCE;
else
option = '?'; /* error */
}
else
newlist->source_addr.in6.sin6_addr = in6addr_any;
}
#endif
else
option = '?'; /* error */
}
if (option == '?')
while (newlist)
{
serv = newlist;
newlist = newlist->next;
free(serv);
}
else
{
serv = newlist;
while (serv->next)
{
serv->next->flags = serv->flags;
serv->next->addr = serv->addr;
serv->next->source_addr = serv->source_addr;
serv = serv->next;
}
serv->next = daemon->servers;
daemon->servers = newlist;
}
break;
}
case 'c':
{
int size;
if (!atoi_check(arg, &size))
option = '?';
else
{
/* zero is OK, and means no caching. */
if (size < 0)
size = 0;
else if (size > 10000)
size = 10000;
daemon->cachesize = size;
}
break;
}
case 'p':
if (!atoi_check(arg, &daemon->port))
option = '?';
break;
case 'P':
{
int i;
if (!atoi_check(arg, &i))
option = '?';
daemon->edns_pktsz = (unsigned short)i;
break;
}
case 'Q':
if (!atoi_check(arg, &daemon->query_port))
option = '?';
break;
case 'T':
{
int ttl;
if (!atoi_check(arg, &ttl))
option = '?';
else
daemon->local_ttl = (unsigned long)ttl;
break;
}
case 'X':
if (!atoi_check(arg, &daemon->dhcp_max))
option = '?';
break;
case 'F':
{
int k, leasepos = 2;
char *cp, *a[5] = { NULL, NULL, NULL, NULL, NULL };
struct dhcp_context *new = safe_malloc(sizeof(struct dhcp_context));
new->next = daemon->dhcp;
new->lease_time = DEFLEASE;
new->addr_epoch = 0;
new->netmask.s_addr = 0;
new->broadcast.s_addr = 0;
new->router.s_addr = 0;
new->netid.net = NULL;
new->flags = 0;
problem = "bad dhcp-range";
if (!arg)
{
option = '?';
break;
}
for (cp = arg; *cp; cp++)
if (!(*cp == ' ' || *cp == '.' || (*cp >='0' && *cp <= '9')))
break;
if (*cp != ',' && (comma = strchr(arg, ',')))
{
*comma = 0;
if (strstr(arg, "net:") == arg)
{
new->netid.net = safe_string_alloc(arg+4);
new->netid.next = NULL;
new->flags |= CONTEXT_FILTER;
}
else
new->netid.net = safe_string_alloc(arg);
a[0] = comma + 1;
}
else
a[0] = arg;
for (k = 1; k < 5; k++)
{
if (!(a[k] = strchr(a[k-1], ',')))
break;
*(a[k]++) = 0;
}
if ((k < 2) || ((new->start.s_addr = inet_addr(a[0])) == (in_addr_t)-1))
option = '?';
else if (strcmp(a[1], "static") == 0)
{
new->end = new->start;
new->flags |= CONTEXT_STATIC;
}
else if ((new->end.s_addr = inet_addr(a[1])) == (in_addr_t)-1)
option = '?';
if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr))
{
struct in_addr tmp = new->start;
new->start = new->end;
new->end = tmp;
}
if (option != '?' && k >= 3 && strchr(a[2], '.') &&
((new->netmask.s_addr = inet_addr(a[2])) != (in_addr_t)-1))
{
new->flags |= CONTEXT_NETMASK;
leasepos = 3;
if (!is_same_net(new->start, new->end, new->netmask))
{
problem = "inconsistent DHCP range";
option = '?';
}
}
if (option == '?')
{
free(new);
break;
}
else
daemon->dhcp = new;
if (k >= 4 && strchr(a[3], '.') &&
((new->broadcast.s_addr = inet_addr(a[3])) != (in_addr_t)-1))
{
new->flags |= CONTEXT_BRDCAST;
leasepos = 4;
}
if (k >= leasepos+1)
{
if (strcmp(a[leasepos], "infinite") == 0)
new->lease_time = 0xffffffff;
else
{
int fac = 1;
if (strlen(a[leasepos]) > 0)
{
switch (a[leasepos][strlen(a[leasepos]) - 1])
{
case 'd':
case 'D':
fac *= 24;
/* fall though */
case 'h':
case 'H':
fac *= 60;
/* fall through */
case 'm':
case 'M':
fac *= 60;
/* fall through */
case 's':
case 'S':
a[leasepos][strlen(a[leasepos]) - 1] = 0;
}
new->lease_time = atoi(a[leasepos]) * fac;
/* Leases of a minute or less confuse
some clients, notably Apple's */
if (new->lease_time < 120)
new->lease_time = 120;
}
}
}
if (new->lease_time < daemon->min_leasetime)
daemon->min_leasetime = new->lease_time;
break;
}
case 'G':
{
int j, k;
char *a[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
struct dhcp_config *new = safe_malloc(sizeof(struct dhcp_config));
struct in_addr in;
new->next = daemon->dhcp_conf;
new->flags = 0;
if ((a[0] = arg))
for (k = 1; k < 6; k++)
{
if (!(a[k] = strchr(a[k-1], ',')))
break;
*(a[k]++) = 0;
}
else
k = 0;
for (j = 0; j < k; j++)
if (strchr(a[j], ':')) /* ethernet address, netid or binary CLID */
{
char *arg = a[j];
if ((arg[0] == 'i' || arg[0] == 'I') &&
(arg[1] == 'd' || arg[1] == 'D') &&
arg[2] == ':')
{
if (arg[3] == '*')
new->flags |= CONFIG_NOCLID;
else
{
int len;
arg += 3; /* dump id: */
if (strchr(arg, ':'))
len = parse_hex(arg, (unsigned char *)arg, -1, NULL);
else
len = (int) strlen(arg);
new->flags |= CONFIG_CLID;
new->clid_len = len;
new->clid = safe_malloc(len);
memcpy(new->clid, arg, len);
}
}
else if (strstr(arg, "net:") == arg)
{
new->flags |= CONFIG_NETID;
new->netid.net = safe_string_alloc(arg+4);
}
else if (parse_hex(a[j], new->hwaddr, 6, &new->wildcard_mask) == 6)
new->flags |= CONFIG_HWADDR;
else
option = '?';
}
else if (strchr(a[j], '.') && (in.s_addr = inet_addr(a[j])) != (in_addr_t)-1)
{
new->addr = in;
new->flags |= CONFIG_ADDR;
}
else
{
char *cp, *lastp = NULL, last = 0;
int fac = 1;
if (strlen(a[j]) > 1)
{
lastp = a[j] + strlen(a[j]) - 1;
last = *lastp;
switch (last)
{
case 'd':
case 'D':
fac *= 24;
/* fall through */
case 'h':
case 'H':
fac *= 60;
/* fall through */
case 'm':
case 'M':
fac *= 60;
/* fall through */
case 's':
case 'S':
*lastp = 0;
}
}
for (cp = a[j]; *cp; cp++)
if (!isdigit(*cp) && *cp != ' ')
break;
if (*cp)
{
if (lastp)
*lastp = last;
if (strcmp(a[j], "infinite") == 0)
{
new->lease_time = 0xffffffff;
new->flags |= CONFIG_TIME;
}
else if (strcmp(a[j], "ignore") == 0)
new->flags |= CONFIG_DISABLE;
else
{
new->hostname = safe_string_alloc(a[j]);
new->flags |= CONFIG_NAME;
}
}
else
{
new->lease_time = atoi(a[j]) * fac;
/* Leases of a minute or less confuse
some clients, notably Apple's */
if (new->lease_time < 120)
new->lease_time = 120;
new->flags |= CONFIG_TIME;
}
}
if (option == '?')
{
problem = "bad dhcp-host";
if (new->flags & CONFIG_NAME)
free(new->hostname);
if (new->flags & CONFIG_CLID)
free(new->clid);
if (new->flags & CONFIG_NETID)
free(new->netid.net);
free(new);
}
else
{
if ((new->flags & CONFIG_TIME) && new->lease_time < daemon->min_leasetime)
daemon->min_leasetime = new->lease_time;
daemon->dhcp_conf = new;
}
break;
}
case 'O':
{
struct dhcp_opt *new = safe_malloc(sizeof(struct dhcp_opt));
char lenchar = 0, *cp;
int addrs, digs, is_addr, is_hex, is_dec;
new->len = 0;
new->is_addr = 0;
new->netid = NULL;
new->val = NULL;
new->vendor_class = NULL;
if ((comma = safe_strchr(arg, ',')))
{
struct dhcp_netid *np = NULL;
*comma++ = 0;
do {
for (cp = arg; *cp; cp++)
if (!(*cp == ' ' || (*cp >='0' && *cp <= '9')))
break;
if (!*cp)
break;
if (strstr(arg, "vendor:") == arg)
new->vendor_class = (unsigned char *)safe_string_alloc(arg+7);
else
{
new->netid = safe_malloc(sizeof (struct dhcp_netid));
new->netid->net = safe_string_alloc(arg);
new->netid->next = np;
np = new->netid;
}
arg = comma;
if ((comma = safe_strchr(arg, ',')))
*comma++ = 0;
} while (arg);
}
if (!arg || (new->opt = atoi(arg)) == 0)
{
option = '?';
problem = "bad dhcp-option";
}
else if (comma && new->opt == 119 && !new->vendor_class)
{
/* dns search, RFC 3397 */
unsigned char *q, *r, *tail;
unsigned char *p = NULL;
size_t newlen, len = 0;
arg = comma;
if ((comma = safe_strchr(arg, ',')))
*(comma++) = 0;
while (arg && *arg)
{
if (!canonicalise_opt(arg))
{
option = '?';
problem = "bad domain in dhcp-option";
break;
}
if (!(p = realloc(p, len + strlen(arg) + 2)))
die("could not get memory", NULL);
q = p + len;
/* add string on the end in RFC1035 format */
while (*arg)
{
unsigned char *cp = q++;
int j;
for (j = 0; *arg && (*arg != '.'); arg++, j++)
*q++ = *arg;
*cp = j;
if (*arg)
arg++;
}
*q++ = 0;
/* Now tail-compress using earlier names. */
newlen = q - p;
for (tail = p + len; *tail; tail += (*tail) + 1)
for (r = p; r - p < (int)len; r += (*r) + 1)
if (strcmp((char *)r, (char *)tail) == 0)
{
PUTSHORT((r - p) | 0xc000, tail);
newlen = tail - p;
goto end;
}
end:
len = newlen;
arg = comma;
if ((comma = safe_strchr(arg, ',')))
*(comma++) = 0;
}
new->len = (int) len;
new->val = p;
}
else if (comma)
{
/* not option 119 */
/* characterise the value */
is_addr = is_hex = is_dec = 1;
addrs = digs = 1;
for (cp = comma; *cp; cp++)
if (*cp == ',')
{
addrs++;
is_dec = is_hex = 0;
}
else if (*cp == ':')
{
digs++;
is_dec = is_addr = 0;
}
else if (*cp == '.')
is_dec = is_hex = 0;
else if (!((*cp >='0' && *cp <= '9') || *cp == '-'))
{
is_addr = 0;
if (cp[1] == 0 && is_dec &&
(*cp == 'b' || *cp == 's' || *cp == 'i'))
{
lenchar = *cp;
*cp = 0;
}
else
is_dec = 0;
if (!((*cp >='A' && *cp <= 'F') ||
(*cp >='a' && *cp <= 'f')))
is_hex = 0;
}
if (is_hex && digs > 1)
{
new->len = digs;
new->val = safe_malloc(new->len);
parse_hex(comma, new->val, digs, NULL);
}
else if (is_dec)
{
int i, val = atoi(comma);
/* assume numeric arg is 1 byte except for
options where it is known otherwise.
For vendor class option, we have to hack. */
new->len = 1;
if (lenchar == 'b')
new->len = 1;
else if (lenchar == 's')
new->len = 2;
else if (lenchar == 'i')
new->len = 4;
else if (new->vendor_class)
{
if (val & 0xffff0000)
new->len = 4;
else if (val & 0xff00)
new->len = 2;
}
else
switch (new->opt)
{
case 13: case 22: case 25: case 26:
new->len = 2;
break;
case 2: case 24: case 35: case 38:
new->len = 4;
break;
}
new->val = safe_malloc(new->len);
for (i=0; i<new->len; i++)
new->val[i] = val>>((new->len - i - 1)*8);
}
else if (is_addr)
{
struct in_addr in;
unsigned char *op;
new->len = INADDRSZ * addrs;
new->val = op = safe_malloc(new->len);
new->is_addr = 1;
while (addrs--)
{
cp = comma;
if ((comma = strchr(cp, ',')))
*comma++ = 0;
in.s_addr = inet_addr(cp);
memcpy(op, &in, INADDRSZ);
op += INADDRSZ;
}
}
else
{
/* text arg */
new->len = strlen(comma);
/* keep terminating zero on string */
new->val = (unsigned char *)safe_string_alloc(comma);
}
}
if (new->len > 255)
{
option = '?';
problem = "dhcp-option too long";
}
if (option == '?')
{
if (new->netid)
free(new->netid);
if (new->val)
free(new->val);
if (new->vendor_class)
free(new->vendor_class);
free(new);
}
else if (new->vendor_class)
{
new->next = daemon->vendor_opts;
daemon->vendor_opts = new;
}
else
{
new->next = daemon->dhcp_opts;
daemon->dhcp_opts = new;
}
break;
}
case 'M':
{
struct dhcp_netid *id = NULL;
while (arg && strstr(arg, "net:") == arg)
{
struct dhcp_netid *newid = safe_malloc(sizeof(struct dhcp_netid));
newid->next = id;
id = newid;
if ((comma = strchr(arg, ',')))
*comma++ = 0;
newid->net = safe_string_alloc(arg+4);
arg = comma;
};
if (!arg)
option = '?';
else
{
char *dhcp_file, *dhcp_sname = NULL;
struct in_addr dhcp_next_server;
if ((comma = strchr(arg, ',')))
*comma++ = 0;
dhcp_file = safe_string_alloc(arg);
dhcp_next_server.s_addr = 0;
if (comma)
{
arg = comma;
if ((comma = strchr(arg, ',')))
*comma++ = 0;
dhcp_sname = safe_string_alloc(arg);
if (comma)
{
unhide_metas(comma);
if ((dhcp_next_server.s_addr = inet_addr(comma)) == (in_addr_t)-1)
option = '?';
}
}
if (option != '?')
{
struct dhcp_boot *new = safe_malloc(sizeof(struct dhcp_boot));
new->file = dhcp_file;
new->sname = dhcp_sname;
new->next_server = dhcp_next_server;
new->netid = id;
new->next = daemon->boot_config;
daemon->boot_config = new;
}
}
if (option == '?')
{
struct dhcp_netid *tmp;
for (; id; id = tmp)
{
tmp = id->next;
free(id);
}
}
break;
}
case 'U':
case 'j':
{
if (!(comma = safe_strchr(arg, ',')))
option = '?';
else
{
struct dhcp_vendor *new = safe_malloc(sizeof(struct dhcp_vendor));
*comma = 0;
new->netid.net = safe_string_alloc(arg);
unhide_metas(comma+1);
new->len = strlen(comma+1);
new->data = safe_malloc(new->len);
memcpy(new->data, comma+1, new->len);
new->is_vendor = (option == 'U');
new->next = daemon->dhcp_vendors;
daemon->dhcp_vendors = new;
}
break;
}
case 'J':
{
struct dhcp_netid_list *new = safe_malloc(sizeof(struct dhcp_netid_list));
struct dhcp_netid *list = NULL;
new->next = daemon->dhcp_ignore;
daemon->dhcp_ignore = new;
do {
struct dhcp_netid *member = safe_malloc(sizeof(struct dhcp_netid));
if ((comma = safe_strchr(arg, ',')))
*comma++ = 0;
member->next = list;
list = member;
member->net = safe_string_alloc(arg);
arg = comma;
} while (arg);
new->list = list;
break;
}
case 'V':
{
char *a[3] = { NULL, NULL, NULL };
int k;
struct in_addr in, out, mask;
struct doctor *new;
mask.s_addr = 0xffffffff;
if ((a[0] = arg))
for (k = 1; k < 3; k++)
{
if (!(a[k] = strchr(a[k-1], ',')))
break;
*(a[k]++) = 0;
unhide_metas(a[k]);
}
else
k = 0;
if ((k < 2) ||
((in.s_addr = inet_addr(a[0])) == (in_addr_t)-1) ||
((out.s_addr = inet_addr(a[1])) == (in_addr_t)-1))
{
option = '?';
break;
}
if (k == 3)
mask.s_addr = inet_addr(a[2]);
new = safe_malloc(sizeof(struct doctor));
new->in = in;
new->out = out;
new->mask = mask;
new->next = daemon->doctors;
daemon->doctors = new;
break;
}
case 'Y':
{
struct txt_record *new;
unsigned char *p, *q;
if ((comma = safe_strchr(arg, ',')))
*(comma) = 0;
if (!canonicalise_opt(arg))
{
option = '?';
problem = "bad TXT record";
break;
}
if ((q = (unsigned char *)comma))
while (1)
{
size_t len;
if ((p = (unsigned char *)strchr((char*)q+1, ',')))
{
if ((len = p - q - 1) > 255)
{
option = '?';
break;
}
*q = len;
for (q = q+1; q < p; q++)
*q = unhide_meta(*q);
}
else
{
if ((len = strlen((char *)q+1)) > 255)
option = '?';
*q = len;
for (q = q+1; *q; q++)
*q = unhide_meta(*q);
break;
}
}
if (option == '?')
{
problem = "TXT record string too long";
break;
}
new = safe_malloc(sizeof(struct txt_record));
new->next = daemon->txt;
daemon->txt = new;
new->class = C_IN;
if (comma)
{
new->len = q - ((unsigned char *)comma);
new->txt = safe_malloc(new->len);
memcpy(new->txt, comma, new->len);
}
else
{
static char empty[] = "";
new->len = 1;
new->txt = empty;
}
if (comma)
*comma = 0;
new->name = safe_string_alloc(arg);
break;
}
case 'W':
{
int port = 1, priority = 0, weight = 0;
char *name, *target = NULL;
struct mx_srv_record *new;
if ((comma = safe_strchr(arg, ',')))
*(comma++) = 0;
if (!canonicalise_opt(arg))
{
option = '?';
problem = "bad SRV record";
break;
}
name = safe_string_alloc(arg);
if (comma)
{
arg = comma;
if ((comma = strchr(arg, ',')))
*(comma++) = 0;
if (!canonicalise_opt(arg))
{
option = '?';
problem = "bad SRV target";
break;
}
target = safe_string_alloc(arg);
if (comma)
{
arg = comma;
if ((comma = strchr(arg, ',')))
*(comma++) = 0;
if (!atoi_check(arg, &port))
{
option = '?';
problem = "invalid port number";
break;
}
if (comma)
{
arg = comma;
if ((comma = strchr(arg, ',')))
*(comma++) = 0;
if (!atoi_check(arg, &priority))
{
option = '?';
problem = "invalid priority";
break;
}
if (comma)
{
arg = comma;
if ((comma = strchr(arg, ',')))
*(comma++) = 0;
if (!atoi_check(arg, &weight))
{
option = '?';
problem = "invalid weight";
break;
}
}
}
}
}
new = safe_malloc(sizeof(struct mx_srv_record));
new->next = daemon->mxnames;
daemon->mxnames = new;
new->issrv = 1;
new->name = name;
new->target = target;
new->srvport = port;
new->priority = priority;
new->weight = weight;
break;
}
}
}
if (option == '?')
{
if (f)
complain( problem ? problem : "error", lineno, conffile);
else
#ifdef HAVE_GETOPT_LONG
die("bad command line options: %s.", problem ? problem : "try --help");
#else
die("bad command line options: %s.", problem ? problem : "try -w");
#endif
}
}
/* port might no be known when the address is parsed - fill in here */
if (daemon->servers)
{
struct server *tmp;
for (tmp = daemon->servers; tmp; tmp = tmp->next)
if (!(tmp->flags & SERV_HAS_SOURCE))
{
if (tmp->source_addr.sa.sa_family == AF_INET)
tmp->source_addr.in.sin_port = htons(daemon->query_port);
#ifdef HAVE_IPV6
else if (tmp->source_addr.sa.sa_family == AF_INET6)
tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
#endif
}
}
if (daemon->if_addrs)
{
struct iname *tmp;
for(tmp = daemon->if_addrs; tmp; tmp = tmp->next)
if (tmp->addr.sa.sa_family == AF_INET)
tmp->addr.in.sin_port = htons(daemon->port);
#ifdef HAVE_IPV6
else if (tmp->addr.sa.sa_family == AF_INET6)
tmp->addr.in6.sin6_port = htons(daemon->port);
#endif /* IPv6 */
}
/* only one of these need be specified: the other defaults to the host-name */
if ((daemon->options & OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget)
{
struct mx_srv_record *mx;
if (gethostname(buff, MAXDNAME) == -1)
die("cannot get host-name: %s", NULL);
for (mx = daemon->mxnames; mx; mx = mx->next)
if (!mx->issrv && hostname_isequal(mx->name, buff))
break;
if ((daemon->mxtarget || (daemon->options & OPT_LOCALMX)) && !mx)
{
mx = safe_malloc(sizeof(struct mx_srv_record));
mx->next = daemon->mxnames;
mx->issrv = 0;
mx->target = NULL;
mx->name = safe_string_alloc(buff);
daemon->mxnames = mx;
}
if (!daemon->mxtarget)
daemon->mxtarget = safe_string_alloc(buff);
for (mx = daemon->mxnames; mx; mx = mx->next)
if (!mx->issrv && !mx->target)
mx->target = daemon->mxtarget;
}
if (daemon->options & OPT_NO_RESOLV)
daemon->resolv_files = 0;
else if (daemon->resolv_files &&
(daemon->resolv_files)->next &&
(daemon->options & OPT_NO_POLL))
die("only one resolv.conf file allowed in no-poll mode.", NULL);
if (daemon->options & OPT_RESOLV_DOMAIN)
{
char *line;
if (!daemon->resolv_files || (daemon->resolv_files)->next)
die("must have exactly one resolv.conf to read domain from.", NULL);
if (!(f = fopen((daemon->resolv_files)->name, "r")))
die("failed to read %s: %m", (daemon->resolv_files)->name);
while ((line = fgets(buff, MAXDNAME, f)))
{
char *token = strtok(line, " \t\n\r");
if (!token || strcmp(token, "search") != 0)
continue;
if ((token = strtok(NULL, " \t\n\r")) &&
canonicalise_opt(token) &&
(daemon->domain_suffix = safe_string_alloc(token)))
break;
}
fclose(f);
if (!daemon->domain_suffix)
die("no search directive found in %s", (daemon->resolv_files)->name);
}
if (daemon->domain_suffix)
{
/* add domain for any srv record without one. */
struct mx_srv_record *srv;
for (srv = daemon->mxnames; srv; srv = srv->next)
if (srv->issrv &&
strchr(srv->name, '.') &&
strchr(srv->name, '.') == strrchr(srv->name, '.'))
{
strcpy(buff, srv->name);
strcat(buff, ".");
strcat(buff, daemon->domain_suffix);
free(srv->name);
srv->name = safe_string_alloc(buff);
}
}
return daemon;
}