Skip to content

Commit

Permalink
Follow the Microsoft DNS client behaviour much more closely.
Browse files Browse the repository at this point in the history
I have completely redone the update logic to match that documented in
the MMC help (Dns/Concepts/Understanding DNS/Dynamic update).

The client also reads configuration variables from the file
$(sysconfdir)/dnsupdate.conf if it exists. This is intended for
use with group policy.
  • Loading branch information
dleonard committed Aug 11, 2008
1 parent a3b05cd commit 9d33350
Show file tree
Hide file tree
Showing 16 changed files with 1,512 additions and 159 deletions.
4 changes: 3 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
1.3.0.?
- bug 484: detect the authoritative domain name by doing a query
- use configuration file for options similar to Microsoft's DNS client
- follow NS records when primary SOA update fails
- bug 484: detect the authoritative domain name by doing an SOA query
- bug 467: add -r option for PTR updates
- bug 415: Debian and Ubuntu support
- fix option processing bug
Expand Down
26 changes: 22 additions & 4 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ sbin_PROGRAMS = dnsupdate
libexec_SCRIPTS = dnsupdate-install-hooks
man_MANS = dnsupdate.8 dnsupdate-install-hooks.8

dnsupdate_SOURCES= dnsupdate.c dns.c dnstcp.c \
dnsdebug.c dnstkey.c dnstsig.c \
common.h dns.h dnsdebug.h dnstcp.h \
dnstkey.h dnstsig.h
dnsupdate_SOURCES= dnsupdate.c
dnsupdate_SOURCES+= common.h
dnsupdate_SOURCES+= dns.c dns.h
dnsupdate_SOURCES+= dnsdebug.c dnsdebug.h
dnsupdate_SOURCES+= dnstcp.c dnstcp.h
dnsupdate_SOURCES+= dnstkey.c dnstkey.h
dnsupdate_SOURCES+= dnstsig.c dnstsig.h
dnsupdate_SOURCES+= conf.c conf.h
dnsupdate_SOURCES+= stream.c stream.h
dnsupdate_SOURCES+= resconf.c resconf.h
dnsupdate_SOURCES+= list.c list.h
dnsupdate_LDADD= $(LIBOBJS)
dnsupdate_CFLAGS= $(VAS_CFLAGS)
dnsupdate_LDFLAGS= $(VAS_LIBS)
dnsupdate_CPPFLAGS= -DPATH_SYSCONFDIR=\"$(sysconfdir)\"

EXTRA_DIST= $(man_MANS) err.h \
dnsupdate-install-hooks.in dnsupdate.pp pp
Expand Down Expand Up @@ -49,3 +57,13 @@ SUBDIRS=
if WITH_IPWATCHD
SUBDIRS+= ipwatchd
endif

check_PROGRAMS= conf-t
conf_t_SOURCES= conf-t.c conf.c conf.h stream.c stream.h
conf_t_CFLAGS = -ggdb

check_PROGRAMS+= list-t
list_t_SOURCES= list-t.c list.c list.h
list_t_CFLAGS = -ggdb

TESTS= $(check_PROGRAMS)
73 changes: 73 additions & 0 deletions conf-t.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

/* Unit tests for conf */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "conf.h"

#define TESTFILEPATH "/tmp/_conf_test.txt"

int main()
{
FILE *f;

config_add("test", "1");
assert(config_get_int("test", 2) == 1);
assert(strcmp(config_get_string("test", "2"), "1") == 0);
assert(config_get_int("unset", 2) == 2);
assert(config_get_int("unset", -1) == -1);
assert(strcmp(config_get_string("unset", "2"), "2") == 0);
assert(config_get_string("unset", NULL) == NULL);

config_add("test4", "4");
assert(config_get_int("test4", 2) == 4);
assert(strcmp(config_get_string("test4", "2"), "4") == 0);
assert(config_get_int("test", 2) == 1);
assert(strcmp(config_get_string("test", "2"), "1") == 0);
assert(config_get_int("unset", 2) == 2);
assert(strcmp(config_get_string("unset", "2"), "2") == 0);

config_add("test", "3");
assert(config_get_int("test", 2) == 3);
assert(strcmp(config_get_string("test", "2"), "3") == 0);
assert(config_get_int("unset", 2) == 2);
assert(strcmp(config_get_string("unset", "2"), "2") == 0);

config_add("test", "0x10");
assert(config_get_int("test", 99) == 0x10);
config_add("test", "077");
assert(config_get_int("test", 99) == 077);

config_add("test", NULL);
assert(config_get_string("test", "") == NULL);

f = fopen(TESTFILEPATH, "w");
assert(f != NULL);
fprintf(f, "a=a\n"
"b=\n"
" \t c = \t c c \t c \t \r\n\r\n"
" badline #=\n" /* generate error but ignore */
" \t #comment\n"
"\n"
" d = d # comment\n"
" e =# comment # #\n"
" # f = something\n"
" g = eol \t");
fclose(f);

config_load(TESTFILEPATH);
(void)unlink(TESTFILEPATH);
assert(strcmp(config_get_string("a", "x"),"a") == 0);
assert(strcmp(config_get_string("b", "x"),"") == 0);
assert(strcmp(config_get_string("c", "x"),"c c \t c") == 0);
assert(strcmp(config_get_string("badline", "x"),"x") == 0);
assert(strcmp(config_get_string("d", "x"),"d") == 0);
assert(strcmp(config_get_string("e", "x"),"") == 0);
assert(strcmp(config_get_string("f", "x"),"x") == 0);
assert(strcmp(config_get_string("g", "x"),"eol") == 0);

exit(0);
}
151 changes: 151 additions & 0 deletions conf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* (c) 2008, Quest Software, Inc. All rights reserved. */

/*
* Simple key-value configuration file interface
*/

#include "common.h"
#include "conf.h"
#include "stream.h"

/* A configuration entry */
struct config {
char *key, *value;
struct config *next;
};

/* Prototypes */
static const struct config *config_get(const char *key);
static void config_load_stream(struct stream *stream);

/* A stack of configuration entries. New config entries are pushed onto the
* top of the stack. Searches are performed top-down into the stack. */
static struct config *Config;

/*------------------------------------------------------------
* Public functions
*/

/* Adds a key/value settings into the global configuration */
void
config_add(char *key, char *value)
{
struct config *config;

if (!(config = (struct config *)malloc(sizeof *config))) {
fprintf(stderr, "config_add: out of memory\n");
exit(1);
}
config->key = key;
config->value = value;
config->next = Config;
Config = config;
}

/* Adds settings from a configuration file into the global configuration. */
void
config_load(const char *path)
{
struct stream stream;

if (!stream_init_path(&stream, path))
return;
config_load_stream(&stream);
stream_fini(&stream);
}

/* Returns a configuration value as an integer.
* Returns def_value if no prior configuration is found.
* The strings 'yes', 'true', and 'on' are converted to 1.
* Numbers beginning with 0x are converted using base 16.
* Numbers beginning with 0 are converted using base 8.
* Non-numeric digits are otherwise ignored.
* A value with no digits is returned as zero. */
long
config_get_int(const char *key, long def_value)
{
const struct config *config;

if (!(config = config_get(key)))
return def_value;
if (strcmp(config->value, "yes") == 0 ||
strcmp(config->value, "true") == 0 ||
strcmp(config->value, "on") == 0)
return 1;
return strtol(config->value, NULL, 0);
}

/* Returns a configuration value as a nul-terminated C string.
* Returns def_value if the configuration is not found.
* Caller must NOT free or alter the returned string. */
const char *
config_get_string(const char *key, const char *def_value)
{
const struct config *config;

if (!(config = config_get(key)))
return def_value;
return config->value;
}

/*------------------------------------------------------------
* Private config functions
*/

/* Returns a configuration entry for the given key, or NULL if not found */
static const struct config *
config_get(const char *key)
{
struct config *config;

for (config = Config; config; config = config->next)
if (strcmp(key, config->key) == 0)
return config;
return NULL;
}

/* Loads configuration statements from the stream into the global Config */
static void
config_load_stream(struct stream *stream)
{
char *key, *value;
struct buffer buffer;

buffer_init(&buffer);

#define WHITESPACE " \t"
#define ENDOFLINE "\n\r"

for (;;) {
/* Ignore to the end of the previous line */
stream_while(stream, ENDOFLINE, NULL); /* skip line end(s) */
stream_while(stream, WHITESPACE, NULL); /* skip lead whitespace */
if (!stream_ok(stream)) /* check for end of file */
break;
if (stream_nextch(stream) == '#') { /* comments start with # */
stream_until(stream, ENDOFLINE, NULL); /* skip to end of line */
continue;
}
buffer.len = 0;
stream_until(stream, "#=" WHITESPACE, &buffer); /* read key word */
if (!buffer.len) {
stream_error(stream, "missing key");
stream_until(stream, ENDOFLINE, NULL); /* skip to end of line */
continue;
}
stream_while(stream, WHITESPACE, NULL); /* skip whitespace */
if (stream_nextch(stream) != '=') { /* expect '=' */
stream_error(stream, "expected '='");
stream_until(stream, ENDOFLINE, NULL); /* skip to end of line */
continue;
}
stream_getch(stream); /* skip '=' */
key = buffer_string(&buffer); /* also clears buffer */
stream_while(stream, WHITESPACE, NULL); /* skip whitespace */
stream_until(stream, "#" ENDOFLINE, &buffer); /* read value */
buffer_rtrim(&buffer, WHITESPACE); /* remove trailing space */
value = buffer_string(&buffer); /* extract value */
config_add(key, value);
}
buffer_fini(&buffer);
}
4 changes: 4 additions & 0 deletions conf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
void config_add(char *key, char *value);
void config_load(const char *path);
long config_get_int(const char *key, long def_value);
const char *config_get_string(const char *key, const char *def_value);
46 changes: 46 additions & 0 deletions dnsdebug.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,38 @@ dumpuint32time(struct dns_msg *msg, const char *name)
fprintf(stderr, "\t%-20s: %.24s (0x%x)\n", name, ctime(&t), v);
}

static void
dumpuint32rtime(struct dns_msg *msg, const char *name)
{
static struct {
const char *abbrev;
uint32_t interval;
} intervals[] = {
{ "d", 24 * 60 * 60 },
{ "h", 60 * 60 },
{ "m", 60 },
{ "s", 1 },
};
uint32_t v,t;
int i, printed_something = 0;

v = dns_rd_uint32(msg);
t = v;
fprintf(stderr, "\t%-20s:", name);
for (i = 0; i < sizeof intervals / sizeof intervals[0]; i++)
if (t >= intervals[i].interval || printed_something) {
int x = t / intervals[i].interval;
if (t) {
fprintf(stderr, " %d%s", x, intervals[i].abbrev);
printed_something = 1;
}
t = t - x * intervals[i].interval;
}
if (!printed_something)
fprintf(stderr, " 0");
fprintf(stderr, " (0x%x)\n", v);
}

static void
dumpheader(const struct dns_header *hdr)
{
Expand Down Expand Up @@ -296,6 +328,20 @@ dumpmsg(struct dns_msg *msg)
dumpdata(msg, "tkey.key", dns_rd_uint16(msg));
dumpdata(msg, "tkey.other", dns_rd_uint16(msg));
dns_rd_end(msg);
} else if (rr.type == 6) {
dns_rd_begin(msg);
dumpname(msg, "soa.mname");
dumpname(msg, "soa.rname");
dumpuint32(msg, "soa.serial");
dumpuint32rtime(msg, "soa.refresh");
dumpuint32rtime(msg, "soa.retry");
dumpuint32rtime(msg, "soa.expire");
dumpuint32(msg, "soa.minttl");
dns_rd_end(msg);
} else if (rr.type == 2) {
dns_rd_begin(msg);
dumpname(msg, "ns.nsdname");
dns_rd_end(msg);
} else {
len = dns_rd_data(msg, data, sizeof data);
if (len)
Expand Down
6 changes: 4 additions & 2 deletions dnstcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ dnstcp_send(int s, const void *buf, size_t len)
return -1;
}
if (verbose > 3)
fprintf(stderr, "dnstcp_send: writing %d bytes to fd %d\n", len, s);
fprintf(stderr, "dnstcp_send: writing %d bytes to fd %d\n",
(int)len, s);
if (len > 0 && write(s, buf, len) != len) {
warn("write");
return -1;
Expand Down Expand Up @@ -198,7 +199,8 @@ dnstcp_recv(int s, void *buf, size_t bufsz)
return 0;
}
if (verbose > 3)
fprintf(stderr, "[read %d of %d header]\n", pos+len, sizeof b);
fprintf(stderr, "[read %d of %d header]\n", pos+len,
(int)sizeof b);
}


Expand Down
17 changes: 17 additions & 0 deletions dnsupdate.8
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dnsupdate \- updates a DNS entry in Active Directory
.RI [\-a\ auth-domain ]
.RI [\-d\ domain ]
.RI [\-h\ hostname ]
.RI [\-o\ option = value ]
.RI [\-s\ nameserver ]
.RI [\-t\ ttl ]
.I ip-addr
Expand Down Expand Up @@ -53,6 +54,9 @@ The reverse name in in-addr.arpa is computed from the
.I ipaddr
and a PTR record update is attempted.
.TP
.RI \-o\ option = value
Overrides an option setting.
.TP
.RI \-s\ nameserver
specifies the nameserver host to send the dynamic DNS update requests to.
The default is to try all domain controllers, using the nearest first.
Expand All @@ -68,6 +72,19 @@ increases the level of verbosity
.TP
\-V
displays version information, then exits.
.SS OPTIONS
Options are read from
/etc/opt/quest/dnsupdate.conf
before argument processing begins.
.TP
.RI UpdateSecurityLevel= integer
A value of zero (default) indicates that an un-authenticated updated is
tried first, and then a secure update only if the first fails.
A value of 16 enables only un-authenticated updates.
A value of 256 enables only authenticated updates.
.TP
.TI DefaultRegistrationTTL= seconds
The default TTL used
.SS "EXIT STATUS"
The
.B dnsupdate
Expand Down
Loading

0 comments on commit 9d33350

Please sign in to comment.