From: Roy Marples Date: Sat, 25 Jan 2014 01:35:53 +0000 (+0000) Subject: Implement RFC 1321 MD5 Message-Digest if not provided in libc. X-Git-Tag: v6.3.0~90 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c73ed1716038133bb4a0efc4f5f96d6c10209c3d;p=thirdparty%2Fdhcpcd.git 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. --- diff --git a/Makefile b/Makefile index 4d831ab4..b6fe6dea 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,15 @@ MKDIRS= 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 @@ HOST_SH?= /bin/sh CLEANFILES+= *.tar.bz2 -.PHONY: import import-bsd dev +.PHONY: import import-bsd dev test .SUFFIXES: .in @@ -95,6 +104,9 @@ depend: .depend ${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 @@ install: proginstall _maninstall _confinstall 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 diff --git a/arp.c b/arp.c index 056e6858..ea8a73c7 100644 --- a/arp.c +++ b/arp.c @@ -231,7 +231,7 @@ arp_announce(void *arg) 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 @@ arp_probe(void *arg) 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) diff --git a/auth.c b/auth.c new file mode 100644 index 00000000..e088bd3e --- /dev/null +++ b/auth.c @@ -0,0 +1,548 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ +} diff --git a/auth.h b/auth.h new file mode 100644 index 00000000..cd5f5443 --- /dev/null +++ b/auth.h @@ -0,0 +1,79 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 + +#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 diff --git a/configure b/configure index 73392d9e..cfb65ff0 100755 --- a/configure +++ b/configure @@ -64,6 +64,7 @@ for x do --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 @@ fi 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 @@ case "$OS" in 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 @@ pselect) ;; esac +if [ -z "$MD5" ]; then + printf "Testing for MD5Init ... " + cat <_md5.c +#include +#include +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 @@ elif [ "$DEV" != no -a "$UDEV" != no ]; then 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 " RUNDIR = $RUNDIR" echo " MANDIR = $MANDIR" echo " HOOKSCRIPTS = $HOOKS" echo + +rm -f dhcpcd tests/test diff --git a/crypt/crypt.h b/crypt/crypt.h new file mode 100644 index 00000000..0a3932e6 --- /dev/null +++ b/crypt/crypt.h @@ -0,0 +1,33 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 diff --git a/crypt/hmac_md5.c b/crypt/hmac_md5.c new file mode 100644 index 00000000..06ea465a --- /dev/null +++ b/crypt/hmac_md5.c @@ -0,0 +1,86 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 +#include + +#include "crypt.h" + +#ifdef HAVE_MD5_H +#include +#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); +} diff --git a/crypt/md5.c b/crypt/md5.c new file mode 100644 index 00000000..9530b88e --- /dev/null +++ b/crypt/md5.c @@ -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 +#include + +#include + +#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<>(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 */ +} + + diff --git a/crypt/md5.h b/crypt/md5.h new file mode 100644 index 00000000..402309c3 --- /dev/null +++ b/crypt/md5.h @@ -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 diff --git a/defs.h b/defs.h index cfc0164d..f477272e 100644 --- a/defs.h +++ b/defs.h @@ -54,5 +54,8 @@ #ifndef CONTROLSOCKET # define CONTROLSOCKET RUNDIR "/" PACKAGE ".sock" #endif +#ifndef RDM_MONOFILE +# define RDM_MONOFILE DBDIR "/" PACKAGE "-rdm.monotonic" +#endif #endif diff --git a/dhcp.c b/dhcp.c index aa237690..e0edf86d 100644 --- a/dhcp.c +++ b/dhcp.c @@ -673,11 +673,12 @@ make_message(struct dhcp_message **message, 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 @@ make_message(struct dhcp_message **message, } *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 @@ make_message(struct dhcp_message **message, *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 @@ write_lease(const struct interface *ifp, const struct dhcp_message *dhcp) } 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 @@ read_lease(const struct interface *ifp) 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 @@ send_message(struct interface *iface, int type, 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 @@ dhcp_drop(struct interface *ifp, const char *reason) 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 @@ dhcp_handledhcp(struct interface *iface, struct dhcp_message **dhcpp, 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) && diff --git a/dhcp.h b/dhcp.h index 8aed9584..b1777f85 100644 --- a/dhcp.h +++ b/dhcp.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2013 Roy Marples + * Copyright (c) 2006-2014 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ #include #include +#include "auth.h" #include "dhcp-common.h" /* UDP port numbers for DHCP */ @@ -105,6 +106,7 @@ enum DHO { 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 @@ struct dhcp_state { time_t start_uptime; unsigned char *clientid; + + struct authstate auth; }; #define D_STATE(ifp) \ @@ -267,7 +271,7 @@ ssize_t make_message(struct dhcp_message **, const struct interface *, 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 *, diff --git a/dhcp6.c b/dhcp6.c index 8d4a22a3..65039c84 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -356,8 +356,9 @@ dhcp6_makemessage(struct interface *ifp) 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 @@ dhcp6_makemessage(struct interface *ifp) 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 @@ dhcp6_makemessage(struct interface *ifp) } } + /* 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 @@ dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) 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 @@ dhcp6_startrequest(struct interface *ifp) syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); return; } + dhcp6_sendrequest(ifp); } @@ -1069,6 +1095,7 @@ dhcp6_startconfirm(struct interface *ifp) 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 @@ dhcp6_readlease(struct interface *ifp) 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 @@ dhcp6_readlease(struct interface *ifp) } } + /* 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 @@ dhcp6_handledata(__unused void *arg) } } + /* 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 @@ dhcp6_freedrop(struct interface *ifp, int drop, const char *reason) free(state->recv); free(state->new); free(state->old); + free(state->auth.reconf); free(state); ifp->if_data[IF_DATA_DHCP6] = NULL; } diff --git a/dhcp6.h b/dhcp6.h index 120500c2..dc2b527f 100644 --- a/dhcp6.h +++ b/dhcp6.h @@ -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 @@ struct dhcp6_state { uint8_t sla_set; char leasefile[PATH_MAX]; const char *reason; + + struct authstate auth; }; #define D6_STATE(ifp) \ diff --git a/dhcpcd-definitions.conf b/dhcpcd-definitions.conf index 9fdfb146..84260861 100644 --- a/dhcpcd-definitions.conf +++ b/dhcpcd-definitions.conf @@ -111,6 +111,14 @@ define 87 string nds_context 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 9 binhex dhcp_relay_msg 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 diff --git a/dhcpcd.8.in b/dhcpcd.8.in index 6c724ce9..5a1c2503 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -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 @@ option described above. 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 @@ running on the .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. diff --git a/dhcpcd.c b/dhcpcd.c index d6dfa067..70210f11 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -460,6 +460,11 @@ configure_interface1(struct interface *ifp) } } #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 diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 8834befa..a901aa0d 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -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 @@ Example: .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 @@ alongside. .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 @@ References an option from the global definition .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 , diff --git a/if-options.c b/if-options.c index 2ec5778e..d30220e7 100644 --- a/if-options.c +++ b/if-options.c @@ -27,6 +27,7 @@ #include #include +#include #include @@ -80,6 +81,9 @@ unsigned long long options = 0; #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 @@ const struct option cf_options[] = { {"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 @@ parse_string_hwaddr(char *sbuf, ssize_t slen, const char *str, int clid) } 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 @@ parse_iaid(uint8_t *iaid, const char *arg, size_t len) 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 @@ parse_iaid(uint8_t *iaid, const char *arg, size_t len) 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 @@ strskipwhite(const char *s) 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 @@ parse_option(const char *ifname, struct if_options *ifo, 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 @@ parse_option(const char *ifname, struct if_options *ifo, 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 @@ read_config(const char *file, 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 @@ free_options(struct if_options *ifo) size_t i; struct dhcp_opt *opt; struct vivco *vo; + struct token *token; if (ifo) { if (ifo->environ) { @@ -1848,6 +2027,13 @@ free_options(struct if_options *ifo) #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); } } diff --git a/if-options.h b/if-options.h index e50f9867..c0a3e80c 100644 --- a/if-options.h +++ b/if-options.h @@ -36,6 +36,8 @@ #include #include +#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 @@ struct if_options { size_t vivco_len; struct dhcp_opt *vivso_override; size_t vivso_override_len; + + struct auth auth; }; extern unsigned long long options; diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..5f3881c8 --- /dev/null +++ b/test/Makefile @@ -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} diff --git a/test/test.c b/test/test.c new file mode 100644 index 00000000..fd8d5b4f --- /dev/null +++ b/test/test.c @@ -0,0 +1,38 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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; +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 00000000..0ca61823 --- /dev/null +++ b/test/test.h @@ -0,0 +1,32 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 diff --git a/test/test_hmac_md5.c b/test/test_hmac_md5.c new file mode 100644 index 00000000..1da71a25 --- /dev/null +++ b/test/test_hmac_md5.c @@ -0,0 +1,173 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 +#include + +#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; +}