changeset 2243:1ba38c1aef87 draft

Implement RFC 1321 MD5 Message-Digest if not provided in libc. Implement RFC 2104 HMAC Keyed Hashing. Implement RFC 3118 Authentication for DHCP Messages and RFC 3315 Authentication options.
author Roy Marples <roy@marples.name>
date Sat, 25 Jan 2014 01:35:53 +0000
parents 7d1ae3a89215
children 253f689f57bf
files Makefile arp.c auth.c auth.h configure crypt/crypt.h crypt/hmac_md5.c crypt/md5.c crypt/md5.h defs.h dhcp.c dhcp.h dhcp6.c dhcp6.h dhcpcd-definitions.conf dhcpcd.8.in dhcpcd.c dhcpcd.conf.5.in if-options.c if-options.h test/Makefile test/test.c test/test.h test/test_hmac_md5.c
diffstat 24 files changed, 1819 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Jan 24 11:09:39 2014 +0000
+++ b/Makefile	Sat Jan 25 01:35:53 2014 +0000
@@ -11,6 +11,15 @@
 include config.mk
 CFLAGS+=	-std=${CSTD}
 
+SRCS+=		${DHCPCD_SRCS}
+
+.PATH: ./crypt
+
+VPATH=	. ./crypt
+
+CPPFLAGS+=	-I./crypt
+SRCS+=		auth.c hmac_md5.c ${MD5_SRC}
+
 OBJS+=		${SRCS:.c=.o} ${COMPAT_SRCS:.c=.o}
 
 SCRIPT=		${LIBEXECDIR}/dhcpcd-run-hooks
@@ -58,7 +67,7 @@
 
 CLEANFILES+=	*.tar.bz2
 
-.PHONY:		import import-bsd dev
+.PHONY:		import import-bsd dev test
 
 .SUFFIXES:	.in
 
@@ -95,6 +104,9 @@
 ${PROG}: ${DEPEND} ${OBJS}
 	${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
 
+test:
+	cd $@; ${MAKE} $@; ./$@
+
 _embeddedinstall: dhcpcd-definitions.conf
 	${INSTALL} -d ${DESTDIR}${SCRIPTSDIR}
 	${INSTALL} -m ${CONFMODE} dhcpcd-definitions.conf ${DESTDIR}${SCRIPTSDIR}
@@ -126,7 +138,7 @@
 
 clean:
 	rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
-	for x in ${SUBDIRS}; do cd $$x; ${MAKE} $@; cd ..; done
+	for x in ${SUBDIRS} test; do cd $$x; ${MAKE} $@; cd ..; done
 
 distclean: clean
 	rm -f .depend config.h config.mk
--- a/arp.c	Fri Jan 24 11:09:39 2014 +0000
+++ b/arp.c	Sat Jan 25 01:35:53 2014 +0000
@@ -231,7 +231,7 @@
 	if (++state->claims < ANNOUNCE_NUM)
 		syslog(LOG_DEBUG,
 		    "%s: sending ARP announce (%d of %d), "
-		    "next in %d.00 seconds",
+		    "next in %d.0 seconds",
 		    ifp->name, state->claims, ANNOUNCE_NUM, ANNOUNCE_WAIT);
 	else
 		syslog(LOG_DEBUG,
@@ -317,7 +317,7 @@
 			eloop_timeout_add_tv(&tv, dhcp_bind, ifp);
 	}
 	syslog(LOG_DEBUG,
-	    "%s: sending ARP probe (%d of %d), next in %0.2f seconds",
+	    "%s: sending ARP probe (%d of %d), next in %0.1f seconds",
 	    ifp->name, state->probes ? state->probes : PROBE_NUM, PROBE_NUM,
 	    timeval_to_double(&tv));
 	if (arp_send(ifp, ARPOP_REQUEST, 0, addr.s_addr) == -1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth.c	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,548 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/file.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "config.h"
+#include "auth.h"
+#include "crypt/crypt.h"
+#include "dhcp.h"
+#include "dhcp6.h"
+#include "dhcpcd.h"
+
+#ifndef htonll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static inline uint64_t
+htonll(uint64_t x)
+{
+
+	return (uint64_t)htonl((uint32_t)(x >> 32)) |
+	    (int64_t)htonl((uint32_t)(x & 0xffffffff)) << 32;
+}
+#else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define htonll(x) (x)
+#endif
+#endif  /* htonll */
+
+#ifndef ntohll
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+static inline uint64_t
+ntohll(uint64_t x)
+{
+
+	return (uint64_t)ntohl((uint32_t)(x >> 32)) |
+	    (int64_t)ntohl((uint32_t)(x & 0xffffffff)) << 32;
+}
+#else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
+#define ntohll(x) (x)
+#endif
+#endif  /* ntohll */
+
+#define HMAC_LENGTH	16
+
+/*
+ * Authenticate a DHCP message.
+ * m and mlen refer to the whole message.
+ * t is the DHCP type, pass it 4 or 6.
+ * data and dlen refer to the authentication option within the message.
+ */
+const struct token *
+dhcp_auth_validate(struct authstate *state, const struct auth *auth,
+    const uint8_t *m, unsigned int mlen, int mp,  int mt,
+    const uint8_t *data, unsigned int dlen)
+{
+	uint8_t protocol, algorithm, rdm, *mm, type;
+	uint64_t replay;
+	uint32_t secretid;
+	const uint8_t *d, *realm;
+	unsigned int realm_len;
+	const struct token *t;
+	time_t now;
+	uint8_t hmac[HMAC_LENGTH];
+
+	if (dlen < 3 + sizeof(replay)) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
+	if (data < m || data > m + mlen || data + dlen > m + mlen) {
+		errno = ERANGE;
+		return NULL;
+	}
+
+	d = data;
+	protocol = *d++;
+	algorithm = *d++;
+	rdm = *d++;
+	if (!(auth->options & DHCPCD_AUTH_SEND)) {
+		/* If we didn't send any authorisation, it can only be a
+		 * reconfigure key */
+		if (protocol != AUTH_PROTO_RECONFKEY) {
+			errno = EINVAL;
+			return NULL;
+		}
+	} else if (protocol != auth->protocol ||
+		    algorithm != auth->algorithm ||
+		    rdm != auth->rdm)
+	{
+		errno = EPERM;
+		return NULL;
+	}
+
+	dlen -= 3;
+	memcpy(&replay, d, sizeof(replay));
+	replay = ntohll(replay);
+	d+= sizeof(replay);
+	dlen -= sizeof(replay);
+
+	if (state->token && replay - state->replay <= 0) {
+		/* Replay attack detected */
+		errno = EPERM;
+		return NULL;
+	}
+
+	realm = NULL;
+	realm_len = 0;
+
+	/* Extract realm and secret.
+	 * Rest of data is MAC. */
+	switch (protocol) {
+	case AUTH_PROTO_TOKEN:
+		secretid = 0;
+		break;
+	case AUTH_PROTO_DELAYED:
+		if (dlen < sizeof(secretid) + sizeof(hmac)) {
+			errno = EINVAL;
+			return NULL;
+		}
+		memcpy(&secretid, d, sizeof(secretid));
+		d += sizeof(secretid);
+		dlen -= sizeof(secretid);
+		break;
+	case AUTH_PROTO_DELAYEDREALM:
+		if (dlen < sizeof(secretid) + sizeof(hmac)) {
+			errno = EINVAL;
+			return NULL;
+		}
+		realm_len = dlen - (sizeof(secretid) + sizeof(hmac));
+		if (realm_len) {
+			realm = d;
+			d += realm_len;
+			dlen -= realm_len;
+		}
+		memcpy(&secretid, d, sizeof(secretid));
+		d += sizeof(secretid);
+		dlen -= sizeof(secretid);
+		break;
+	case AUTH_PROTO_RECONFKEY:
+		if (dlen != 1 + 16) {
+			errno = EINVAL;
+			return NULL;
+		}
+		type = *d++;
+		dlen--;
+		switch (type) {
+		case 1:
+			if ((mp == 4 && mt == DHCP_ACK) ||
+			    (mp == 6 && mt == DHCP6_REPLY))
+			{
+				if (state->reconf == NULL) {
+					state->reconf =
+					    malloc(sizeof(*state->reconf));
+					if (state->reconf == NULL)
+						return NULL;
+					state->reconf->key = malloc(16);
+					if (state->reconf->key == NULL) {
+						free(state->reconf);
+						state->reconf = NULL;
+						return NULL;
+					}
+					state->reconf->secretid = 0;
+					state->reconf->expire = 0;
+					state->reconf->realm = NULL;
+					state->reconf->realm_len = 0;
+					state->reconf->key_len = 16;
+				}
+				memcpy(state->reconf->key, d, 16);
+			} else {
+				errno = EINVAL;
+				return NULL;
+			}
+			if (state->reconf == NULL)
+				errno = ENOENT;
+			/* Nothing to validate, just accepting the key */
+			return state->reconf;
+		case 2:
+			if (state->reconf == NULL) {
+				errno = ENOENT;
+				return NULL;
+			}
+			t = state->reconf;
+			goto gottoken;
+		default:
+			errno = EINVAL;
+			return NULL;
+		}
+	default:
+		errno = ENOTSUP;
+		return NULL;
+	}
+
+	/* Find a token for the realm and secret */
+	secretid = ntohl(secretid);
+	TAILQ_FOREACH(t, &auth->tokens, next) {
+		if (t->secretid == secretid &&
+		    t->realm_len == realm_len &&
+		    (t->realm_len == 0 ||
+		    memcmp(t->realm, realm, t->realm_len) == 0))
+			break;
+	}
+	if (t == NULL) {
+		errno = ESRCH;
+		return NULL;
+	}
+	if (t->expire) {
+		if (time(&now) == -1)
+			return NULL;
+		if (t->expire < now) {
+			errno = EFAULT;
+			return NULL;
+		}
+	}
+
+gottoken:
+	/* First message from the server */
+	if (state->token && state->token != t) {
+		errno = EPERM;
+		return NULL;
+	}
+
+	/* Special case as no hashing needs to be done. */
+	if (protocol == AUTH_PROTO_TOKEN) {
+		if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
+			errno = EPERM;
+			return NULL;
+		}
+		goto finish;
+	}
+
+	/* Make a duplicate of the message, but zero out the MAC part */
+	mm = malloc(mlen);
+	if (mm == NULL)
+		return NULL;
+	memcpy(mm, m, mlen);
+	memset(mm + (d - m), 0, dlen);
+
+	/* RFC3318, section 5.2 - zero giaddr and hops */
+	if (mp == 4) {
+		*(mm + offsetof(struct dhcp_message, hwopcount)) = '\0';
+		memset(mm + offsetof(struct dhcp_message, giaddr), 0, 4);
+	}
+
+	memset(hmac, 0, sizeof(hmac));
+	switch (algorithm) {
+	case AUTH_ALG_HMAC_MD5:
+		hmac_md5(mm, mlen, t->key, t->key_len, hmac);
+		break;
+	default:
+		errno = ENOSYS;
+		free(mm);
+		return NULL;
+	}
+
+	free(mm);
+	if (memcmp(d, &hmac, dlen)) {
+		errno = EPERM;
+		return NULL;
+	}
+
+finish:
+	/* If we got here then authentication passed */
+	state->replay = replay;
+	state->token = t;
+
+	return t;
+}
+
+static uint64_t last_rdm;
+static uint8_t last_rdm_set;
+static uint64_t
+get_next_rdm_monotonic(void)
+{
+	FILE *fp;
+	char *line, *ep;
+	uint64_t rdm;
+	int flocked;
+
+	fp = fopen(RDM_MONOFILE, "r+");
+	if (fp == NULL) {
+		if (errno != ENOENT)
+			return ++last_rdm; /* report error? */
+		fp = fopen(RDM_MONOFILE, "w");
+		if (fp == NULL)
+			return ++last_rdm; /* report error? */
+		flocked = flock(fileno(fp), LOCK_EX);
+		rdm = 0;
+	} else {
+		flocked = flock(fileno(fp), LOCK_EX);
+		line = get_line(fp);
+		if (line == NULL)
+			rdm = 0; /* truncated? report error? */
+		else
+			rdm = strtoull(line, &ep, 0);
+	}
+
+	rdm++;
+	fseek(fp, 0, SEEK_SET);
+	if (fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19) {
+		if (!last_rdm_set) {
+			last_rdm = rdm;
+			last_rdm_set = 1;
+		} else
+			rdm = ++last_rdm;
+		/* report error? */
+	}
+	fflush(fp);
+	if (flocked == 0)
+		flock(fileno(fp), LOCK_UN);
+	fclose(fp);
+	return rdm;
+}
+
+
+/*
+ * Encode a DHCP message.
+ * Either we know which token to use from the server response
+ * or we are using a basic configuration token.
+ * token is the token to encrypt with.
+ * m and mlen refer to the whole message.
+ * mp is the DHCP type, pass it 4 or 6.
+ * mt is the DHCP message type.
+ * data and dlen refer to the authentication option within the message.
+ */
+int
+dhcp_auth_encode(const struct auth *auth, const struct token *t,
+    uint8_t *m, unsigned int mlen, int mp, int mt,
+    uint8_t *data, unsigned int dlen)
+{
+	uint64_t rdm;
+	uint8_t hmac[HMAC_LENGTH];
+	time_t now;
+	uint8_t hops, *p;
+	uint32_t giaddr, secretid;
+
+	if (auth->protocol == 0 && t == NULL) {
+		TAILQ_FOREACH(t, &auth->tokens, next) {
+			if (t->secretid == 0 &&
+			    t->realm_len == 0)
+			break;
+		}
+		if (t == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+		if (t->expire) {
+			if (time(&now) == -1)
+				return -1;
+			if (t->expire < now) {
+				errno = EPERM;
+				return -1;
+			}
+		}
+	}
+
+	switch(auth->protocol) {
+	case AUTH_PROTO_TOKEN:
+	case AUTH_PROTO_DELAYED:
+	case AUTH_PROTO_DELAYEDREALM:
+		/* We don't ever send a reconf key */
+		break;
+	default:
+		errno = ENOTSUP;
+		return -1;
+	}
+
+	switch(auth->algorithm) {
+	case AUTH_ALG_HMAC_MD5:
+		break;
+	default:
+		errno = ENOTSUP;
+		return -1;
+	}
+
+	switch(auth->rdm) {
+	case AUTH_RDM_MONOTONIC:
+		break;
+	default:
+		errno = ENOTSUP;
+		return -1;
+	}
+
+	/* Work out the auth area size.
+	 * We only need to do this for DISCOVER messages */
+	if (data == NULL) {
+		dlen = 1 + 1 + 1 + 8;
+		switch(auth->protocol) {
+		case AUTH_PROTO_TOKEN:
+			dlen += t->key_len;
+			break;
+		case AUTH_PROTO_DELAYEDREALM:
+			if (t)
+				dlen += t->realm_len;
+			/* FALLTHROUGH */
+		case AUTH_PROTO_DELAYED:
+			if (t)
+				dlen += sizeof(t->secretid) + sizeof(hmac);
+			break;
+		}
+		return dlen;
+	}
+
+	if (dlen < 1 + 1 + 1 + 8) {
+		errno = ENOBUFS;
+		return -1;
+	}
+
+	/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
+	if (data < m || data > m + mlen || data + dlen > m + mlen) {
+		errno = ERANGE;
+		return -1;
+	}
+
+	/* Write out our option */
+	*data++ = auth->protocol;
+	*data++ = auth->algorithm;
+	*data++ = auth->rdm;
+	switch (auth->rdm) {
+	case AUTH_RDM_MONOTONIC:
+		rdm = get_next_rdm_monotonic();
+		break;
+	default:
+		/* This block appeases gcc, clang doesn't need it */
+		rdm = get_next_rdm_monotonic();
+		break;
+	}
+	rdm = htonll(rdm);
+	memcpy(data, &rdm, 8);
+	data += 8;
+	dlen -= 1 + 1 + 1 + 8;
+
+	/* Special case as no hashing needs to be done. */
+	if (auth->protocol == AUTH_PROTO_TOKEN) {
+		/* Should be impossible, but still */
+		if (t == NULL) {
+			errno = EINVAL;
+			return -1;
+		}
+		if (dlen < t->key_len) {
+			errno =	ENOBUFS;
+			return -1;
+		}
+		memcpy(data, t->key, t->key_len);
+		return dlen - t->key_len;
+	}
+
+	/* DISCOVER or INFORM messages don't write auth info */
+	if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
+	    (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
+		return dlen;
+
+	/* Loading a saved lease without an authentication option */
+	if (t == NULL)
+		return 0;
+
+	/* Write out the Realm */
+	if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
+		if (dlen < t->realm_len) {
+			errno = ENOBUFS;
+			return -1;
+		}
+		memcpy(data, t->realm, t->realm_len);
+		data += t->realm_len;
+		dlen -= t->realm_len;
+	}
+
+	/* Write out the SecretID */
+	if (auth->protocol == AUTH_PROTO_DELAYED ||
+	    auth->protocol == AUTH_PROTO_DELAYEDREALM)
+	{
+		if (dlen < sizeof(t->secretid)) {
+			errno = ENOBUFS;
+			return -1;
+		}
+		secretid = htonl(t->secretid);
+		memcpy(data, &secretid, sizeof(secretid));
+		data += sizeof(secretid);
+		dlen -= sizeof(secretid);
+	}
+
+	/* Zero what's left, the MAC */
+	memset(data, 0, dlen);
+
+	/* RFC3318, section 5.2 - zero giaddr and hops */
+	if (mp == 4) {
+		p = m + offsetof(struct dhcp_message, hwopcount);
+		hops = *p;
+		*p = '\0';
+		p = m + offsetof(struct dhcp_message, giaddr);
+		memcpy(&giaddr, p, sizeof(giaddr));
+		memset(p, 0, sizeof(giaddr));
+	} else {
+		/* appease GCC again */
+		hops = 0;
+		giaddr = 0;
+	}
+
+	/* Create our hash and write it out */
+	switch(auth->algorithm) {
+	case AUTH_ALG_HMAC_MD5:
+		hmac_md5(m, mlen, t->key, t->key_len, hmac);
+		memcpy(data, hmac, sizeof(hmac));
+		break;
+	}
+
+	/* RFC3318, section 5.2 - restore giaddr and hops */
+	if (mp == 4) {
+		p = m + offsetof(struct dhcp_message, hwopcount);
+		*p = hops;
+		p = m + offsetof(struct dhcp_message, giaddr);
+		memcpy(p, &giaddr, sizeof(giaddr));
+	}
+
+	/* Done! */
+	return dlen - sizeof(hmac); /* should be zero */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth.h	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,79 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef AUTH_H
+#define AUTH_H
+
+#include <sys/queue.h>
+
+#define DHCPCD_AUTH_SEND	(1 << 0)
+#define DHCPCD_AUTH_REQUIRE	(1 << 1)
+
+#define AUTH_PROTO_TOKEN	0
+#define AUTH_PROTO_DELAYED	1
+#define AUTH_PROTO_DELAYEDREALM	2
+#define AUTH_PROTO_RECONFKEY	3
+
+#define AUTH_ALG_HMAC_MD5	1
+
+#define AUTH_RDM_MONOTONIC	0
+
+struct token {
+	TAILQ_ENTRY(token) next;
+	uint32_t secretid;
+	unsigned int realm_len;
+	unsigned char *realm;
+	unsigned int key_len;
+	unsigned char *key;
+	time_t expire;
+};
+
+TAILQ_HEAD(token_head, token);
+
+struct auth {
+	int options;
+	uint8_t protocol;
+	uint8_t algorithm;
+	uint8_t rdm;
+	struct token_head tokens;
+};
+
+struct authstate {
+	uint64_t replay;
+	const struct token *token;
+	struct token *reconf;
+};
+
+const struct token * dhcp_auth_validate(struct authstate *,
+    const struct auth *,
+    const uint8_t *, unsigned int, int, int,
+    const uint8_t *, unsigned int);
+
+int dhcp_auth_encode(const struct auth *, const struct token *,
+    uint8_t *, unsigned int, int, int,
+    uint8_t *, unsigned int);
+#endif
--- a/configure	Fri Jan 24 11:09:39 2014 +0000
+++ b/configure	Sat Jan 25 01:35:53 2014 +0000
@@ -64,6 +64,7 @@
         --without-posix_spawn) POSIX_SPAWN=no;;
 	--without-pollts) POLLTS=no;;
 	--with-pollts) POLLTS=$var;;
+	--without-md5) MD5=no;;
 	--without-dev) DEV=no;;
 	--without-udev) UDEV=no;;
 	--serviceexists) SERVICEEXISTS=$var;;
@@ -319,7 +320,7 @@
 
 if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then
 	echo "dhcpcd-definitions.conf will be embedded in dhcpcd itself"
-	echo "SRCS+=		dhcpcd-embedded.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	dhcpcd-embedded.c" >>$CONFIG_MK
 else
 	echo "dhcpcd-definitions.conf will be installed to $LIBEXECDIR"
 	echo "CFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK
@@ -335,36 +336,36 @@
 linux)
 	echo "CPPFLAGS+=	-D_BSD_SOURCE -D_XOPEN_SOURCE=700" >>$CONFIG_MK
 	if [ -z "$INET" -o "$INET" = yes ]; then
-		echo "SRCS+=		lpf.c" >>$CONFIG_MK
+		echo "DHCPCD_SRCS+=	lpf.c" >>$CONFIG_MK
 	fi
-	echo "SRCS+=		if-linux.c if-linux-wireless.c" >>$CONFIG_MK
-	echo "SRCS+=		platform-linux.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	if-linux.c if-linux-wireless.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	platform-linux.c" >>$CONFIG_MK
 	echo "LDADD+=		-lrt -ldl" >>$CONFIG_MK
 	;;
 kfreebsd)
 	echo "CPPFLAGS+=	-D_GNU_SOURCE" >>$CONFIG_MK
 	if [ -z "$INET" -o "$INET" = yes ]; then
-		echo "SRCS+=		bpf.c" >>$CONFIG_MK
+		echo "DHCPCD_SRCS+=		bpf.c" >>$CONFIG_MK
 	fi
-	echo "SRCS+=		if-bsd.c platform-bsd.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	if-bsd.c platform-bsd.c" >>$CONFIG_MK
 	echo "COMPAT_SRCS+=	compat/linkaddr.c" >>$CONFIG_MK
 	echo "LDADD+=		-lrt -ldl" >>$CONFIG_MK
 	;;
 *)
 	if [ -z "$INET" -o "$INET" = yes ]; then
-		echo "SRCS+=		bpf.c" >>$CONFIG_MK
+		echo "DHCPCD_SRCS+=		bpf.c" >>$CONFIG_MK
 	fi
-	echo "SRCS+=		if-bsd.c platform-bsd.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	if-bsd.c platform-bsd.c" >>$CONFIG_MK
 	;;
 esac
 
 if [ -z "$INET" -o "$INET" = yes ]; then
 	echo "CPPFLAGS+=	-DINET" >>$CONFIG_MK
-	echo "SRCS+=		arp.c dhcp.c ipv4.c ipv4ll.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	arp.c dhcp.c ipv4.c ipv4ll.c" >>$CONFIG_MK
 fi
 if [ -z "$INET6" -o "$INET6" = yes ]; then
 	echo "CPPFLAGS+=	-DINET6" >>$CONFIG_MK
-	echo "SRCS+=		ipv6.c ipv6nd.c dhcp6.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	ipv6.c ipv6nd.c dhcp6.c" >>$CONFIG_MK
 fi
 
 # NetBSD: Even if we build for $PREFIX, the clueless user might move us to /
@@ -624,6 +625,32 @@
 	;;
 esac
 
+if [ -z "$MD5" ]; then
+	printf "Testing for MD5Init ... "
+	cat <<EOF >_md5.c
+#include <md5.h>
+#include <stdlib.h>
+int main(void) {
+	MD5_CTX context;
+	MD5Init(&context);
+	return 0;
+}
+EOF
+	if $XCC _md5.c -o _md5 2>/dev/null; then
+		MD5=yes
+	else
+		MD5=no
+	fi
+	echo "$MD5"
+	rm -f _md5.c _md5
+fi
+if [ "$MD5" = no ]; then
+	echo "MD5_SRC=	md5.c" >>$CONFIG_MK
+else
+	echo "MD5_SRC=" >>$CONFIG_MK
+	echo "CPPFLAGS+=	-DHAVE_MD5H" >>$CONFIG_H
+fi
+
 if [ "$DEV" != no -a "$UDEV" != no ]; then
 	printf "Checking for libudev ... "
 	LIBUDEV_CFLAGS=$(pkg-config --cflags libudev 2>/dev/null)
@@ -678,7 +705,7 @@
 fi
 
 if [ "$DEV" = yes ]; then
-	echo "SRCS+=		dev.c" >>$CONFIG_MK
+	echo "DHCPCD_SRCS+=	dev.c" >>$CONFIG_MK
 	echo "CPPFLAGS+=	-DPLUGIN_DEV" >>$CONFIG_MK
 	echo "MKDIRS+=	dev" >>$CONFIG_MK
 fi
@@ -772,3 +799,5 @@
 echo "   MANDIR =		$MANDIR"
 echo "   HOOKSCRIPTS =	$HOOKS"
 echo
+
+rm -f dhcpcd tests/test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/crypt/crypt.h	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,33 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef CRYPT_H
+#define CRYPT_H
+
+void hmac_md5(const uint8_t *, int, const uint8_t *, int, uint8_t *);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/crypt/hmac_md5.c	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,86 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "crypt.h"
+
+#ifdef HAVE_MD5_H
+#include <md5.h>
+#else
+#include "md5.h"
+#endif
+
+#define HMAC_PAD_LEN	64
+#define IPAD		0x36
+#define OPAD		0x5C
+
+/* hmac_md5 as per RFC3118 */
+void
+hmac_md5(const uint8_t *text, int text_len,
+    const uint8_t *key, int key_len,
+    uint8_t *digest)
+{
+	uint8_t k_ipad[HMAC_PAD_LEN], k_opad[HMAC_PAD_LEN];
+	uint8_t tk[MD5_DIGEST_LENGTH];
+	int i;
+	MD5_CTX context;
+
+	/* Ensure key is no bigger than HMAC_PAD_LEN */
+	if (key_len > HMAC_PAD_LEN) {
+		MD5Init(&context);
+		MD5Update(&context, key, key_len);
+		MD5Final(tk, &context);
+		key = tk;
+		key_len = MD5_DIGEST_LENGTH;
+	}
+
+	/* store key in pads */
+	memcpy(k_ipad, key, key_len);
+	memcpy(k_opad, key, key_len);
+	memset(k_ipad + key_len, 0, sizeof(k_ipad) - key_len);
+	memset(k_opad + key_len, 0, sizeof(k_opad) - key_len);
+
+	/* XOR key with ipad and opad values */
+	for (i = 0; i < HMAC_PAD_LEN; i++) {
+		k_ipad[i] ^= IPAD;
+		k_opad[i] ^= OPAD;
+	}
+
+	/* inner MD5 */
+	MD5Init(&context);
+	MD5Update(&context, k_ipad, HMAC_PAD_LEN);
+	MD5Update(&context, text, text_len);
+	MD5Final(digest, &context);
+
+	/* outer MD5 */
+	MD5Init(&context);
+	MD5Update(&context, k_opad, HMAC_PAD_LEN);
+	MD5Update(&context, digest, MD5_DIGEST_LENGTH);
+	MD5Final(digest, &context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/crypt/md5.c	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,242 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.	This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include <sys/param.h>
+#include <inttypes.h>
+
+#include <string.h>
+
+#include "md5.h"
+
+#define PUT_64BIT_LE(cp, value) do {					\
+	(cp)[7] = (value) >> 56;					\
+	(cp)[6] = (value) >> 48;					\
+	(cp)[5] = (value) >> 40;					\
+	(cp)[4] = (value) >> 32;					\
+	(cp)[3] = (value) >> 24;					\
+	(cp)[2] = (value) >> 16;					\
+	(cp)[1] = (value) >> 8;						\
+	(cp)[0] = (value); } while (0)
+
+#define PUT_32BIT_LE(cp, value) do {					\
+	(cp)[3] = (value) >> 24;					\
+	(cp)[2] = (value) >> 16;					\
+	(cp)[1] = (value) >> 8;						\
+	(cp)[0] = (value); } while (0)
+
+static uint8_t PADDING[MD5_BLOCK_LENGTH] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(MD5_CTX *ctx)
+{
+	ctx->count = 0;
+	ctx->state[0] = 0x67452301;
+	ctx->state[1] = 0xefcdab89;
+	ctx->state[2] = 0x98badcfe;
+	ctx->state[3] = 0x10325476;
+}
+
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void
+MD5Transform(uint32_t state[4], const uint8_t block[MD5_BLOCK_LENGTH])
+{
+	uint32_t a, b, c, d, in[MD5_BLOCK_LENGTH / 4];
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+	memcpy(in, block, sizeof(in));
+#else
+	for (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) {
+		in[a] = (uint32_t)(
+		    (uint32_t)(block[a * 4 + 0]) |
+		    (uint32_t)(block[a * 4 + 1]) <<  8 |
+		    (uint32_t)(block[a * 4 + 2]) << 16 |
+		    (uint32_t)(block[a * 4 + 3]) << 24);
+	}
+#endif
+
+	a = state[0];
+	b = state[1];
+	c = state[2];
+	d = state[3];
+
+	MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478,  7);
+	MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12);
+	MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17);
+	MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22);
+	MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf,  7);
+	MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12);
+	MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17);
+	MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22);
+	MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8,  7);
+	MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12);
+	MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+	MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+	MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122,  7);
+	MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+	MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+	MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+	MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562,  5);
+	MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340,  9);
+	MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+	MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20);
+	MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d,  5);
+	MD5STEP(F2, d, a, b, c, in[10] + 0x02441453,  9);
+	MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+	MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20);
+	MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6,  5);
+	MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6,  9);
+	MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14);
+	MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20);
+	MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905,  5);
+	MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8,  9);
+	MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14);
+	MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+	MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942,  4);
+	MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11);
+	MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+	MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+	MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44,  4);
+	MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11);
+	MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16);
+	MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+	MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6,  4);
+	MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11);
+	MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16);
+	MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23);
+	MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039,  4);
+	MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+	MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+	MD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23);
+
+	MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244,  6);
+	MD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10);
+	MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+	MD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21);
+	MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3,  6);
+	MD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10);
+	MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+	MD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21);
+	MD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f,  6);
+	MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+	MD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15);
+	MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+	MD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82,  6);
+	MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+	MD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15);
+	MD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21);
+
+	state[0] += a;
+	state[1] += b;
+	state[2] += c;
+	state[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len)
+{
+	size_t have, need;
+
+	/* Check how many bytes we already have and how many more we need. */
+	have = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
+	need = MD5_BLOCK_LENGTH - have;
+
+	/* Update bitcount */
+	ctx->count += (uint64_t)len << 3;
+
+	if (len >= need) {
+		if (have != 0) {
+			memcpy(ctx->buffer + have, input, need);
+			MD5Transform(ctx->state, ctx->buffer);
+			input += need;
+			len -= need;
+			have = 0;
+		}
+
+		/* Process data in MD5_BLOCK_LENGTH-byte chunks. */
+		while (len >= MD5_BLOCK_LENGTH) {
+			MD5Transform(ctx->state, input);
+			input += MD5_BLOCK_LENGTH;
+			len -= MD5_BLOCK_LENGTH;
+		}
+	}
+
+	/* Handle any remaining bytes of data. */
+	if (len != 0)
+		memcpy(ctx->buffer + have, input, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx)
+{
+	uint8_t count[8];
+	size_t padlen;
+	int i;
+
+	/* Convert count to 8 bytes in little endian order. */
+	PUT_64BIT_LE(count, ctx->count);
+
+	/* Pad out to 56 mod 64. */
+	padlen = MD5_BLOCK_LENGTH -
+	    ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));
+	if (padlen < 1 + 8)
+		padlen += MD5_BLOCK_LENGTH;
+	MD5Update(ctx, PADDING, padlen - 8);		/* padlen - 8 <= 64 */
+	MD5Update(ctx, count, 8);
+
+	if (digest != NULL) {
+		for (i = 0; i < 4; i++)
+			PUT_32BIT_LE(digest + i * 4, ctx->state[i]);
+	}
+	memset(ctx, 0, sizeof(*ctx));	/* in case it's sensitive */
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/crypt/md5.h	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,33 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.	This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#ifndef MD5_H_
+#define MD5_H_
+
+#define MD5_DIGEST_LENGTH	16
+#define MD5_BLOCK_LENGTH	64
+
+typedef struct MD5Context {
+	uint32_t state[4];	/* state (ABCD) */
+	uint64_t count;		/* number of bits, modulo 2^64 (lsb first) */
+	unsigned char buffer[MD5_BLOCK_LENGTH]; /* input buffer */
+} MD5_CTX;
+
+void	MD5Init(MD5_CTX *);
+void	MD5Update(MD5_CTX *, const unsigned char *, size_t);
+void	MD5Final(unsigned char[MD5_DIGEST_LENGTH], MD5_CTX *);
+#endif
--- a/defs.h	Fri Jan 24 11:09:39 2014 +0000
+++ b/defs.h	Sat Jan 25 01:35:53 2014 +0000
@@ -54,5 +54,8 @@
 #ifndef CONTROLSOCKET
 # define CONTROLSOCKET		RUNDIR "/" PACKAGE ".sock"
 #endif
+#ifndef RDM_MONOFILE
+# define RDM_MONOFILE		DBDIR "/" PACKAGE "-rdm.monotonic"
+#endif
 
 #endif
--- a/dhcp.c	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcp.c	Sat Jan 25 01:35:53 2014 +0000
@@ -673,11 +673,12 @@
     uint8_t type)
 {
 	struct dhcp_message *dhcp;
-	uint8_t *m, *lp, *p;
+	uint8_t *m, *lp, *p, *auth;
 	uint8_t *n_params = NULL;
 	uint32_t ul;
 	uint16_t sz;
 	size_t len, i;
+	int auth_len;
 	const struct dhcp_opt *opt;
 	const struct if_options *ifo = iface->options;
 	const struct dhcp_state *state = D_CSTATE(iface);
@@ -916,6 +917,27 @@
 		}
 		*n_params = p - n_params - 1;
 	}
+
+	/* silence GCC */
+	auth_len = 0;
+	auth = NULL;
+
+	if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+		auth_len = dhcp_auth_encode(&ifo->auth, state->auth.token,
+		    NULL, 0, 4, type, NULL, 0);
+		if (auth_len > 0) {
+			len = (p + auth_len) - m;
+			if (auth_len > 255 || len > sizeof(*dhcp))
+				goto toobig;
+			*p++ = DHO_AUTHENTICATION;
+			*p++ = (uint8_t)auth_len;
+			auth = p;
+			p += auth_len;
+		} else if (auth_len == -1)
+			syslog(LOG_ERR, "%s: dhcp_auth_encode: %m",
+			    iface->name);
+	}
+
 	*p++ = DHO_END;
 
 #ifdef BOOTP_MESSAGE_LENTH_MIN
@@ -926,8 +948,13 @@
 		*p++ = DHO_PAD;
 #endif
 
+	len = p - m;
+	if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len > 0)
+		dhcp_auth_encode(&ifo->auth, state->auth.token,
+		    m, len, 4, type, auth, auth_len);
+
 	*message = dhcp;
-	return p - m;
+	return len;
 
 toobig:
 	syslog(LOG_ERR, "%s: DHCP messge too big", iface->name);
@@ -978,12 +1005,15 @@
 }
 
 struct dhcp_message *
-read_lease(const struct interface *ifp)
+read_lease(struct interface *ifp)
 {
 	int fd;
 	struct dhcp_message *dhcp;
-	const struct dhcp_state *state = D_CSTATE(ifp);
+	struct dhcp_state *state = D_STATE(ifp);
 	ssize_t bytes;
+	const uint8_t *auth;
+	uint8_t type;
+	int auth_len;
 
 	fd = open(state->leasefile, O_RDONLY);
 	if (fd == -1) {
@@ -1003,8 +1033,28 @@
 	close(fd);
 	if (bytes < 0) {
 		free(dhcp);
-		dhcp = NULL;
+		return NULL;
 	}
+
+	/* We may have found a BOOTP server */
+	if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1)
+		type = 0;
+	/* Authenticate the message */
+	auth = get_option(dhcp, DHO_AUTHENTICATION, &auth_len);
+	if (auth) {
+		if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+		    (uint8_t *)dhcp, sizeof(*dhcp), 4, type,
+		    auth, auth_len) == NULL)
+		{
+			syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m",
+			    ifp->name);
+			free(dhcp);
+			return NULL;
+		}
+		syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+		    ifp->name, state->auth.token->secretid);
+	}
+
 	return dhcp;
 }
 
@@ -1453,7 +1503,7 @@
 		tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
 		timernorm(&tv);
 		syslog(LOG_DEBUG,
-		    "%s: sending %s (xid 0x%x), next in %0.2f seconds",
+		    "%s: sending %s (xid 0x%x), next in %0.1f seconds",
 		    iface->name, get_dhcp_op(type), state->xid,
 		    timeval_to_double(&tv));
 	}
@@ -1989,6 +2039,10 @@
 	state->old = NULL;
 	state->lease.addr.s_addr = 0;
 	ifp->options->options &= ~ DHCPCD_CSR_WARNED;
+	state->auth.token = NULL;
+	state->auth.replay = 0;
+	free(state->auth.reconf);
+	state->auth.reconf = NULL;
 }
 
 static void
@@ -2066,16 +2120,38 @@
 	struct dhcp_message *dhcp = *dhcpp;
 	struct dhcp_lease *lease = &state->lease;
 	uint8_t type, tmp;
+	const uint8_t *auth;
 	struct in_addr addr;
 	size_t i;
-
-	/* reset the message counter */
-	state->interval = 0;
+	int auth_len;
 
 	/* We may have found a BOOTP server */
 	if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1)
 		type = 0;
 
+	/* Authenticate the message */
+	auth = get_option(dhcp, DHO_AUTHENTICATION, &auth_len);
+	if (auth) {
+		if (dhcp_auth_validate(&state->auth, &ifo->auth,
+		    (uint8_t *)*dhcpp, sizeof(**dhcpp), 4, type,
+		    auth, auth_len) == NULL)
+		{
+			syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m",
+			    iface->name);
+			log_dhcp(LOG_ERR, "authentication failed",
+			    iface, dhcp, from);
+			return;
+		}
+		syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+		    iface->name, state->auth.token->secretid);
+	} else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+		log_dhcp(LOG_ERR, "missing authentiation", iface, dhcp, from);
+		return;
+	}
+
+	/* reset the message counter */
+	state->interval = 0;
+
 	if (type == DHCP_NAK) {
 		/* For NAK, only check if we require the ServerID */
 		if (has_option_mask(ifo->requiremask, DHO_SERVERID) &&
--- a/dhcp.h	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcp.h	Sat Jan 25 01:35:53 2014 +0000
@@ -1,6 +1,6 @@
 /*
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2013 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -34,6 +34,7 @@
 #include <limits.h>
 #include <stdint.h>
 
+#include "auth.h"
 #include "dhcp-common.h"
 
 /* UDP port numbers for DHCP */
@@ -105,6 +106,7 @@
 	DHO_USERCLASS              = 77,  /* RFC 3004 */
 	DHO_RAPIDCOMMIT            = 80,  /* RFC 4039 */
 	DHO_FQDN                   = 81,
+	DHO_AUTHENTICATION         = 90,  /* RFC 3118 */
 	DHO_VIVCO                  = 124, /* RFC 3925 */
 	DHO_VIVSO                  = 125, /* RFC 3925 */
 	DHO_DNSSEARCH              = 119, /* RFC 3397 */
@@ -228,6 +230,8 @@
 	time_t start_uptime;
 
 	unsigned char *clientid;
+
+	struct authstate auth;
 };
 
 #define D_STATE(ifp)							       \
@@ -267,7 +271,7 @@
 int valid_dhcp_packet(unsigned char *);
 
 ssize_t write_lease(const struct interface *, const struct dhcp_message *);
-struct dhcp_message *read_lease(const struct interface *);
+struct dhcp_message *read_lease(struct interface *);
 void get_lease(struct dhcp_lease *, const struct dhcp_message *);
 
 void dhcp_handleifa(int, struct interface *,
--- a/dhcp6.c	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcp6.c	Sat Jan 25 01:35:53 2014 +0000
@@ -356,8 +356,9 @@
 	const struct dhcp6_option *si, *unicast;
 	ssize_t len, ml;
 	size_t l;
+	int auth_len;
 	uint8_t u8;
-	uint16_t *u16, n_options;
+	uint16_t *u16, n_options, type;
 	const struct if_options *ifo;
 	const struct dhcp_opt *opt;
 	uint8_t IA, *p;
@@ -474,48 +475,55 @@
 		m = state->new;
 		ml = state->new_len;
 	}
-
-	state->send = malloc(len);
-	if (state->send == NULL)
-		return -1;
-
-	state->send_len = len;
 	unicast = NULL;
 	/* Depending on state, get the unicast address */
 	switch(state->state) {
 		break;
 	case DH6S_INIT: /* FALLTHROUGH */
 	case DH6S_DISCOVER:
-		state->send->type = DHCP6_SOLICIT;
+		type = DHCP6_SOLICIT;
 		break;
 	case DH6S_REQUEST:
-		state->send->type = DHCP6_REQUEST;
+		type = DHCP6_REQUEST;
 		unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
 		break;
 	case DH6S_CONFIRM:
-		state->send->type = DHCP6_CONFIRM;
+		type = DHCP6_CONFIRM;
 		break;
 	case DH6S_REBIND:
-		state->send->type = DHCP6_REBIND;
+		type = DHCP6_REBIND;
 		break;
 	case DH6S_RENEW:
-		state->send->type = DHCP6_RENEW;
+		type = DHCP6_RENEW;
 		unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
 		break;
 	case DH6S_INFORM:
-		state->send->type = DHCP6_INFORMATION_REQ;
+		type = DHCP6_INFORMATION_REQ;
 		break;
 	case DH6S_RELEASE:
-		state->send->type = DHCP6_RELEASE;
+		type = DHCP6_RELEASE;
 		unicast = dhcp6_getmoption(D6_OPTION_UNICAST, m, ml);
 		break;
 	default:
 		errno = EINVAL;
-		free(state->send);
-		state->send = NULL;
 		return -1;
 	}
 
+	if (ifo->auth.options & DHCPCD_AUTH_SEND) {
+		auth_len = dhcp_auth_encode(&ifo->auth, state->auth.token,
+		    NULL, 0, 6, type, NULL, 0);
+		if (auth_len > 0)
+			len += sizeof(*o) + auth_len;
+	} else
+		auth_len = 0; /* appease GCC */
+
+	state->send = malloc(len);
+	if (state->send == NULL)
+		return -1;
+
+	state->send_len = len;
+	state->send->type = type;
+
 	/* If we found a unicast option, copy it to our state for sending */
 	if (unicast && ntohs(unicast->len) == sizeof(state->unicast.s6_addr))
 		memcpy(&state->unicast.s6_addr, D6_COPTION_DATA(unicast),
@@ -656,6 +664,23 @@
 		}
 	}
 
+	/* This has to be the last option */
+	if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len > 0) {
+		o = D6_NEXT_OPTION(o);
+		o->code = htons(D6_OPTION_AUTH);
+		o->len = htons(auth_len);
+		if (dhcp_auth_encode(&ifo->auth, state->auth.token,
+		    (uint8_t *)state->send, state->send_len,
+		    6, state->send->type,
+		    D6_OPTION_DATA(o), auth_len) == -1) 
+		{
+			printf ("oh dear\n");
+			free(state->send);
+			state->send = NULL;
+			return -1;
+		}
+	}
+
 	return 0;
 }
 
@@ -796,7 +821,7 @@
 logsend:
 		syslog(LOG_DEBUG,
 		    "%s: %s %s (xid 0x%02x%02x%02x),"
-		    " next in %0.2f seconds",
+		    " next in %0.1f seconds",
 		    ifp->name,
 		    broad_uni,
 		    dhcp6_get_op(state->send->type),
@@ -1052,6 +1077,7 @@
 		syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name);
 		return;
 	}
+
 	dhcp6_sendrequest(ifp);
 }
 
@@ -1069,6 +1095,7 @@
 	state->MRT = CNF_MAX_RT;
 	state->MRC = 0;
 
+	syslog(LOG_INFO, "%s: confirming prior DHCPv6 lease", ifp->name);
 	if (dhcp6_makemessage(ifp) == -1) {
 		syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name);
 		return;
@@ -1592,6 +1619,7 @@
 	int fd;
 	ssize_t bytes;
 	struct timeval now;
+	const struct dhcp6_option *o;
 
 	state = D6_STATE(ifp);
 	if (stat(state->leasefile, &st) == -1) {
@@ -1634,6 +1662,27 @@
 		}
 	}
 
+	/* Authenticate the message */
+	o = dhcp6_getmoption(D6_OPTION_AUTH, state->new, state->new_len);
+	if (o) {
+		if (dhcp_auth_validate(&state->auth, &ifp->options->auth,
+		    (uint8_t *)state->new, state->new_len, 6, state->new->type,
+		    D6_COPTION_DATA(o), ntohs(o->len)) == NULL)
+		{
+			syslog(LOG_DEBUG, "%s: dhcp_auth_validate: %m",
+			    ifp->name);
+			syslog(LOG_ERR, "%s: authentication failed",
+			    ifp->name);
+			goto ex;
+		}
+		syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+		    ifp->name, state->auth.token->secretid);
+	} else if (ifp->options->auth.options & DHCPCD_AUTH_REQUIRE) {
+		syslog(LOG_ERR, "%s: authentication now required", ifp->name);
+		goto ex;
+	} else
+		syslog(LOG_ERR, "eg");
+
 	return fd;
 
 ex:
@@ -2065,6 +2114,26 @@
 		}
 	}
 
+	/* Authenticate the message */
+	o = dhcp6_getmoption(D6_OPTION_AUTH, r, len);
+	if (o) {
+		if (dhcp_auth_validate(&state->auth, &ifo->auth,
+		    (uint8_t *)r, len, 6, r->type,
+		    D6_COPTION_DATA(o), ntohs(o->len)) == NULL)
+		{
+			syslog(LOG_DEBUG, "dhcp_auth_validate: %m");
+			syslog(LOG_ERR, "%s: authentication failed from %s",
+			    ifp->name, sfrom);
+			return;
+		}
+		syslog(LOG_DEBUG, "%s: validated using 0x%08" PRIu32,
+		    ifp->name, state->auth.token->secretid);
+	} else if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) {
+		syslog(LOG_ERR, "%s: missing authentiation from %s",
+		    ifp->name, sfrom);
+		return;
+	}
+
 	op = dhcp6_get_op(r->type);
 	switch(r->type) {
 	case DHCP6_REPLY:
@@ -2498,6 +2567,7 @@
 		free(state->recv);
 		free(state->new);
 		free(state->old);
+		free(state->auth.reconf);
 		free(state);
 		ifp->if_data[IF_DATA_DHCP6] = NULL;
 	}
--- a/dhcp6.h	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcp6.h	Sat Jan 25 01:35:53 2014 +0000
@@ -61,6 +61,7 @@
 #define D6_OPTION_IA_ADDR		5
 #define D6_OPTION_PREFERENCE		7
 #define D6_OPTION_ELAPSED		8
+#define D6_OPTION_AUTH			11
 #define D6_OPTION_UNICAST		12
 #define D6_OPTION_STATUS_CODE		13
 #define D6_OPTION_RAPID_COMMIT		14
@@ -197,6 +198,8 @@
 	uint8_t sla_set;
 	char leasefile[PATH_MAX];
 	const char *reason;
+
+	struct authstate auth;
 };
 
 #define D6_STATE(ifp)							       \
--- a/dhcpcd-definitions.conf	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcpcd-definitions.conf	Sat Jan 25 01:35:53 2014 +0000
@@ -111,6 +111,14 @@
 define 88	domain			bcms_controller_names
 define 89	array ipaddress		bcms_controller_address
 
+# DHCP Authentication, RFC3118
+define 90	embed			auth
+embed		byte			protocol
+embed		byte			algorithm
+embed		byte			rdm
+embed		binhex:8		replay
+embed		binhex			information
+
 # DHCP Leasequery, RFC4388
 define 91	uint32			client_last_transaction_time
 define 92	array ipaddress		associated_ip
@@ -190,7 +198,8 @@
 define6 11	embed			auth
 embed		byte			protocol
 embed		byte			algorithm
-embed		binhex:8		replay_detection
+embed		byte			rdm
+embed		binhex:8		replay
 embed		binhex			information
 
 define6 12	ip6address		unicast
--- a/dhcpcd.8.in	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcpcd.8.in	Sat Jan 25 01:35:53 2014 +0000
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd January 18, 2014
+.Dd January 24, 2014
 .Dt DHCPCD 8
 .Os
 .Sh NAME
@@ -629,6 +629,10 @@
 The actual DHCP message send by the server.
 We use this when reading the last
 lease and use the files mtime as when it was issued.
+.It Pa @DBDIR@/dhcpcd-rdm.monotonic
+Stores the monotonic counter used in the
+.Ar replay
+field in Authentication Options.
 .It Pa /var/run/dhcpcd.pid
 Stores the PID of
 .Nm
@@ -647,12 +651,26 @@
 .Xr dhcpcd-run-hooks 8 ,
 .Xr resolvconf 8
 .Sh STANDARDS
-RFC\ 951 RFC\ 1534 RFC\ 2131, RFC\ 2132, RFC\ 2855, RFC\ 3004, RFC\ 3315,
-RFC\ 3361, RFC\ 3633, RFC\ 3396, RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925,
-RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702,
-RFC\ 4074, RFC\ 4861, RFC\ 4833, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106.
+RFC\ 951, RFC\ 1534, RFC\ 2104, RFC\ 2131, RFC\ 2132, RFC\ 2855, RFC\ 3004,
+RFC\ 3118, RFC\ 3315, RFC\ 3361, RFC\ 3633, RFC\ 3396, RFC\ 3397, RFC\ 3442,
+RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361,
+RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833, RFC\ 5227, RFC\ 5942,
+RFC\ 5969, RFC\ 6106.
 .Sh AUTHORS
 .An Roy Marples Aq Mt roy@marples.name
 .Sh BUGS
 Please report them to
 .Lk http://roy.marples.name/projects/dhcpcd
+.Pp
+If authentication is used and the
+.Pa @DBDIR@/dhcpcd-rdm.monotonic
+file is removed or altered then the DHCP server will need it's notion
+of the last replay value
+.Nm
+sent reset.
+We could change this to use a NTP time stamp instead, but it's
+more likely the RTC on this host is broken which would cause the same result.
+.Pp
+WIDE DHCPv6 server sometimes fails to authenticate a
+.Nm
+message.
--- a/dhcpcd.c	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcpcd.c	Sat Jan 25 01:35:53 2014 +0000
@@ -460,6 +460,11 @@
 		}
 	}
 #endif
+
+	/* If we are not sending an authentication option, don't require it */
+	if (!(ifo->auth.options & DHCPCD_AUTH_SEND))
+		ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
+
 }
 
 int
--- a/dhcpcd.conf.5.in	Fri Jan 24 11:09:39 2014 +0000
+++ b/dhcpcd.conf.5.in	Sat Jan 25 01:35:53 2014 +0000
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd January 18, 2014
+.Dd January 24, 2014
 .Dt DHCPCD.CONF 5
 .Os
 .Sh NAME
@@ -69,6 +69,21 @@
 .Pp
 .D1 profile 192.168.0.1
 .D1 static ip_address=192.168.0.10/24
+.It Ic authprotocol Ar protocol Ar algorithm Ar rdm
+Authenticate DHCP messages.
+See the Supported Protocols section.
+.It Ic authtoken Ar secretid Ar realm Ar expire Ar key
+Define a shared key for use in authentication.
+.Ar realm can be "" to for use with the
+.Ar delayed
+prptocol.
+.Ar expire
+is the date the token expires and should be formatted "yyy-mm-dd HH:MM".
+You can use the keyword
+.Ar forever
+or
+.Ar 0
+which means the token never expires.
 .It Ic background
 Background immediately.
 This is useful for startup scripts which don't disable link messages for
@@ -302,6 +317,8 @@
 .It Ic noarp
 Don't send any ARP requests.
 This also disables IPv4LL.
+.It Ic noauthrequired
+Don't require authentication even though we requested it.
 .It Ic nodev
 Don't load
 .Pa /dev
@@ -629,6 +646,40 @@
 .D1 embed uint32 enterprise_number
 .D1 # Options defined for the enterprise number
 .D1 encap 1 ipaddress ipaddress
+.Ss Supported protocols
+.Bl -tag -width -indent
+.It Ic token
+Sends and expects the token with the secretid 0 in each message.
+.It Ic delayedrealm
+Delayed Authentication.
+.Nm dhcpcd
+will send an authentication option with no key or MAC.
+The server will see this option, and select a key for
+.Nm , writing the
+.Ar realm
+and
+.Ar secretid
+in it.
+.Nm dhcpcd
+will then look for a non-expired token with a matching realm and secretid.
+This token is used to authenicate all other messages.
+.It Ic delayed
+Same as above, but without a realm.
+.El
+.Ss Supported algorithms
+If none specified,
+.Ic hmac-md5
+is the default.
+.Bl -tag -width -indent
+.It Ic hmac-md5
+.El
+.Ss Supported Replay Detection Mechanisms
+If none specified,
+.Ic monotonic
+is the default.
+.Bl -tag -width -indent
+.It Ic monotonic
+.El
 .Sh SEE ALSO
 .Xr fnmatch 3 ,
 .Xr if_nametoindex 3 ,
--- a/if-options.c	Fri Jan 24 11:09:39 2014 +0000
+++ b/if-options.c	Sat Jan 25 01:35:53 2014 +0000
@@ -27,6 +27,7 @@
 
 #include <sys/param.h>
 #include <sys/types.h>
+#include <sys/queue.h>
 
 #include <arpa/inet.h>
 
@@ -80,6 +81,9 @@
 #define O_ENCAP			O_BASE + 22
 #define O_VENDOPT		O_BASE + 23
 #define O_VENDCLASS		O_BASE + 24
+#define O_AUTHPROTOCOL		O_BASE + 25
+#define O_AUTHTOKEN		O_BASE + 26
+#define O_AUTHNOTREQUIRED	O_BASE + 27
 
 char *dev_load;
 
@@ -155,6 +159,9 @@
 	{"encap",           required_argument, NULL, O_ENCAP},
 	{"vendopt",         required_argument, NULL, O_VENDOPT},
 	{"vendclass",       required_argument, NULL, O_VENDCLASS},
+	{"authprotocol",    required_argument, NULL, O_AUTHPROTOCOL},
+	{"authtoken",       required_argument, NULL, O_AUTHTOKEN},
+	{"noauthrequired",  no_argument,       NULL, O_AUTHNOTREQUIRED},
 	{NULL,              0,                 NULL, '\0'}
 };
 
@@ -362,7 +369,7 @@
 }
 
 static int
-parse_iaid(uint8_t *iaid, const char *arg, size_t len)
+parse_iaid1(uint8_t *iaid, const char *arg, size_t len, int n)
 {
 	unsigned long l;
 	size_t s;
@@ -372,7 +379,10 @@
 	errno = 0;
 	l = strtoul(arg, &np, 0);
 	if (l <= (unsigned long)UINT32_MAX && errno == 0 && *np == '\0') {
-		u32 = htonl(l);
+		if (n)
+			u32 = htonl(l);
+		else
+			u32 = l;
 		memcpy(iaid, &u32, sizeof(u32));
 		return 0;
 	}
@@ -390,6 +400,20 @@
 	return 0;
 }
 
+static int
+parse_iaid(uint8_t *iaid, const char *arg, size_t len)
+{
+
+	return parse_iaid1(iaid, arg, len, 1);
+}
+
+static int
+parse_uint32(uint32_t *i, const char *arg)
+{
+
+	return parse_iaid1((uint8_t *)i, arg, sizeof(uint32_t), 0);
+}
+
 static char **
 splitv(int *argc, char **argv, const char *arg)
 {
@@ -549,6 +573,28 @@
 	return UNCONST(s);
 }
 
+/* Find the end pointer of a string. */
+static char *
+strend(const char *s)
+{
+
+	s = strskipwhite(s);
+	if (s == NULL)
+		return NULL;
+	if (*s != '"')
+		return strchr(s, ' ');
+	s++;
+	for (; *s != '"' ; s++) {
+		if (*s == '\0')
+			return NULL;
+		if (*s == '\\') {
+			if (*(++s) == '\0')
+				return NULL;
+		}
+	}
+	return UNCONST(++s);
+}
+
 static int
 parse_option(const char *ifname, struct if_options *ifo,
     int opt, const char *arg)
@@ -565,6 +611,7 @@
 	struct dhcp_opt **dop, *ndop;
 	size_t *dop_len, dl;
 	struct vivco *vivco;
+	struct token *token;
 #ifdef INET6
 	size_t sl;
 	struct if_ia *ia;
@@ -1521,6 +1568,135 @@
 		vivco->len = s;
 		vivco->data = (uint8_t *)np;
 		break;
+	case O_AUTHPROTOCOL:
+		fp = strwhite(arg);
+		if (fp)
+			*fp++ = '\0';
+		if (strcasecmp(arg, "token") == 0)
+			ifo->auth.protocol = AUTH_PROTO_TOKEN;
+		else if (strcasecmp(arg, "delayed") == 0)
+			ifo->auth.protocol = AUTH_PROTO_DELAYED;
+		else if (strcasecmp(arg, "delayedrealm") == 0)
+			ifo->auth.protocol = AUTH_PROTO_DELAYEDREALM;
+		else {
+			syslog(LOG_ERR, "%s: unsupported protocol", arg);
+			return -1;
+		}
+		arg = strskipwhite(fp);
+		fp = strwhite(arg);
+		if (arg == NULL) {
+			ifo->auth.options |= DHCPCD_AUTH_SEND;
+			ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+			ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+			break;
+		}
+		if (fp)
+			*fp++ = '\0';
+		if (strcasecmp(arg, "hmacmd5") == 0 ||
+		    strcasecmp(arg, "hmac-md5") == 0)
+			ifo->auth.algorithm = AUTH_ALG_HMAC_MD5;
+		else {
+			syslog(LOG_ERR, "%s: unsupported algorithm", arg);
+			return 1;
+		}
+		arg = fp;
+		if (arg == NULL) {
+			ifo->auth.options |= DHCPCD_AUTH_SEND;
+			ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+			break;
+		}
+		if (strcasecmp(arg, "monotonic") == 0)
+			ifo->auth.rdm = AUTH_RDM_MONOTONIC;
+		else {
+			syslog(LOG_ERR, "%s: unsupported RDM", arg);
+			return -1;
+		}
+		break;
+	case O_AUTHTOKEN:
+		fp = strwhite(arg);
+		if (fp == NULL) {
+			syslog(LOG_ERR, "authtoken requires a realm");
+			return -1;
+		}
+		*fp++ = '\0';
+		token = malloc(sizeof(*token));
+		if (token == NULL) {
+			syslog(LOG_ERR, "%s: %m", __func__);
+			return -1;
+		}
+		if (parse_uint32(&token->secretid, arg) == -1) {
+			syslog(LOG_ERR, "%s: not a number", arg);
+			free(token);
+			return -1;
+		}
+		arg = fp;
+		fp = strend(arg);
+		if (fp == NULL) {
+			syslog(LOG_ERR, "authtoken requies an a key");
+			free(token);
+			return -1;
+		}
+		*fp++ = '\0';
+		token->realm_len = parse_string(NULL, 0, arg);
+		if (token->realm_len) {
+			token->realm = malloc(token->realm_len);
+			if (token->realm == NULL) {
+				free(token);
+				syslog(LOG_ERR, "%s: %m", __func__);
+				return -1;
+			}
+			parse_string((char *)token->realm, token->realm_len,
+			    arg);
+		}
+		arg = fp;
+		fp = strend(arg);
+		if (fp == NULL) {
+			syslog(LOG_ERR, "authtoken requies an an expiry date");
+			free(token->realm);
+			free(token);
+			return -1;
+		}
+		*fp++ = '\0';
+		if (*arg == '"') {
+			arg++;
+			np = strchr(arg, '"');
+			if (np)
+				*np = '\0';
+		}
+		if (strcmp(arg, "0") == 0 || strcasecmp(arg, "forever") == 0)
+			token->expire =0;
+		else {
+			struct tm tm;
+
+			memset(&tm, 0, sizeof(tm));
+			if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) {
+				syslog(LOG_ERR, "%s: invalid date time", arg);
+				free(token->realm);
+				free(token);
+				return -1;
+			}
+			if ((token->expire = mktime(&tm)) == (time_t)-1) {
+				syslog(LOG_ERR, "%s: mktime: %m", __func__);
+				free(token->realm);
+				free(token);
+				return -1;
+			}
+		}
+		arg = fp;
+		token->key_len = parse_string(NULL, 0, arg);
+		if (token->key_len == 0) {
+			syslog(LOG_ERR, "authtoken needs a key");
+			free(token->realm);
+			free(token);
+			return -1;
+		}
+		token->key = malloc(token->key_len);
+		parse_string((char *)token->key, token->key_len, arg);
+		TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next);
+		break;
+	case O_AUTHNOTREQUIRED:
+		ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE;
+		break;
 	default:
 		return 0;
 	}
@@ -1607,6 +1783,8 @@
 	ifo->timeout = DEFAULT_TIMEOUT;
 	ifo->reboot = DEFAULT_REBOOT;
 	ifo->metric = -1;
+	ifo->auth.options |= DHCPCD_AUTH_REQUIRE;
+	TAILQ_INIT(&ifo->auth.tokens);
 	strlcpy(ifo->script, SCRIPT, sizeof(ifo->script));
 
 	ifo->vendorclassid[0] = strlen(vendor);
@@ -1802,6 +1980,7 @@
 	size_t i;
 	struct dhcp_opt *opt;
 	struct vivco *vo;
+	struct token *token;
 
 	if (ifo) {
 		if (ifo->environ) {
@@ -1848,6 +2027,13 @@
 #endif
 		free(ifo->ia);
 
+		while ((token = TAILQ_FIRST(&ifo->auth.tokens))) {
+			TAILQ_REMOVE(&ifo->auth.tokens, token, next);
+			if (token->realm_len)
+				free(token->realm);
+			free(token->key);
+			free(token);
+		}
 		free(ifo);
 	}
 }
--- a/if-options.h	Fri Jan 24 11:09:39 2014 +0000
+++ b/if-options.h	Sat Jan 25 01:35:53 2014 +0000
@@ -36,6 +36,8 @@
 #include <limits.h>
 #include <stdint.h>
 
+#include "auth.h"
+
 /* Don't set any optional arguments here so we retain POSIX
  * compatibility with getopt */
 #define IF_OPTS "46bc:de:f:gh:i:kl:m:no:pqr:s:t:u:v:wxy:z:ABC:DEF:GHI:JKLO:Q:S:TUVW:X:Z:"
@@ -175,6 +177,8 @@
 	size_t vivco_len;
 	struct dhcp_opt *vivso_override;
 	size_t vivso_override_len;
+
+	struct auth auth;
 };
 
 extern unsigned long long options;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/Makefile	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,33 @@
+include ../config.mk
+
+PROG=		test
+SRCS=		test.c
+SRCS+=		test_hmac_md5.c hmac_md5.c ${MD5_SRC}
+
+CFLAGS?=	-O2
+CSTD?=		c99
+CFLAGS+=	-std=${CSTD}
+
+CPPFLAGS+=	-I../crypt
+
+.PATH:		../crypt
+
+VPATH=		. ../crypt
+
+OBJS+=		${SRCS:.c=.o}
+
+.c.o:
+	${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@
+
+all: ${PROG}
+
+clean:
+	rm -f ${OBJS} ${PROG} ${PROG}.core ${CLEANFILES}
+
+.depend: ${SRCS} ${COMPAT_SRCS}
+	${CC} ${CPPFLAGS} -MM ${SRCS} ${COMPAT_SRCS} > .depend
+
+depend: .depend
+
+${PROG}: ${DEPEND} ${OBJS}
+	${CC} ${LDFLAGS} -o $@ ${OBJS} ${LDADD}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test.c	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,38 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "test.h"
+
+int main(void)
+{
+	int r = 0;
+
+	if (test_hmac_md5())
+		r = -1;
+
+	return r;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test.h	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,32 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef TEST_H
+
+int test_hmac_md5(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_hmac_md5.c	Sat Jan 25 01:35:53 2014 +0000
@@ -0,0 +1,173 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2014 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include "../crypt/crypt.h"
+#include "test.h"
+
+/* RFC2202 MD5 implementation */
+
+static void
+print_hmac(uint8_t *hmac)
+{
+	int i;
+
+	printf("digest = 0x");
+	for (i = 0; i < 16; i++)
+		printf("%02x", *hmac++);
+	printf("\n");
+}
+
+static void
+hmac_md5_test1(void)
+{
+	uint8_t hmac[16];
+	const uint8_t text[] = "Hi There";
+	uint8_t key[16];
+	int i;
+
+	printf ("HMAC MD5 Test 1:\t\t");
+	for (i = 0; i < 16; i++)
+		key[i] = 0x0b;
+	hmac_md5(text, 8, key, 16, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x9294727a3638bb1c13f48ef8158bfc9d\n");
+}
+
+static void
+hmac_md5_test2(void)
+{
+	uint8_t hmac[16];
+	const uint8_t text[] = "what do ya want for nothing?";
+	const uint8_t key[] = "Jefe";
+
+	printf("HMAC MD5 Test 2:\t\t");
+	hmac_md5(text, 28, key, 4, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x750c783e6ab0b503eaa863e10a5db738\n");
+}
+
+static void
+hmac_md5_test3(void)
+{
+	uint8_t hmac[16];
+	uint8_t text[50];
+	uint8_t key[16];
+	int i;
+
+	printf ("HMAC MD5 Test 3:\t\t");
+	for (i = 0; i < 50; i++)
+		text[i] = 0xdd;
+	for (i = 0; i < 16; i++)
+		key[i] = 0xaa;
+	hmac_md5(text, 50, key, 16, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x56be34521d144c88dbb8c733f0e8b3f6\n");
+}
+
+static void
+hmac_md5_test4(void)
+{
+	uint8_t hmac[16];
+	uint8_t text[50];
+	uint8_t key[25];
+	int i;
+
+	printf ("HMAC MD5 Test 4:\t\t");
+	for (i = 0; i < 50; i++)
+		text[i] = 0xcd;
+	for (i = 0; i < 25; i++)
+		key[i] = i + 1;
+	hmac_md5(text, 50, key, 25, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x697eaf0aca3a3aea3a75164746ffaa79\n");
+}
+
+static void
+hmac_md5_test5(void)
+{
+	uint8_t hmac[16];
+	const uint8_t text[] = "Test With Truncation";
+	uint8_t key[16];
+	int i;
+
+	printf ("HMAC MD5 Test 5:\t\t");
+	for (i = 0; i < 16; i++)
+		key[i] = 0x0c;
+	hmac_md5(text, 20, key, 16, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x56461ef2342edc00f9bab995690efd4c\n");
+}
+
+static void
+hmac_md5_test6(void)
+{
+	uint8_t hmac[16];
+	const uint8_t text[] = "Test Using Larger Than Block-Size Key - Hash Key First";
+	uint8_t key[80];
+	int i;
+
+	printf ("HMAC MD5 Test 6:\t\t");
+	for (i = 0; i < 80; i++)
+		key[i] = 0xaa;
+	hmac_md5(text, 54, key, 80, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd\n");
+}
+
+static void
+hmac_md5_test7(void)
+{
+	uint8_t hmac[16];
+	const uint8_t text[] = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data";
+	uint8_t key[80];
+	int i;
+
+	printf ("HMAC MD5 Test 7:\t\t");
+	for (i = 0; i < 80; i++)
+		key[i] = 0xaa;
+	hmac_md5(text, 73, key, 80, hmac);
+	print_hmac(hmac);
+	printf("\t\texpected result:\t 0x6f630fad67cda0ee1fb1f562db3aa53e\n");
+}
+
+int test_hmac_md5(void)
+{
+
+	printf ("Starting HMAC MD5 tests...\n\n");
+	hmac_md5_test1();
+	hmac_md5_test2();
+	hmac_md5_test3();
+	hmac_md5_test4();
+	hmac_md5_test5();
+	hmac_md5_test6();
+	hmac_md5_test7();
+	printf("\nConfirm above results visually against RFC 2202.\n");
+	return 0;
+}