diff --git a/ndhc/duiaid.c b/ndhc/duiaid.c new file mode 100644 index 0000000..4c1abef --- /dev/null +++ b/ndhc/duiaid.c @@ -0,0 +1,263 @@ +/* duiaid.c - DUID/IAID storage and generation for clientids + * + * Copyright (c) 2014 Nicholas J. Kain + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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 "duiaid.h" +#include "strl.h" +#include "log.h" +#include "random.h" +#include "io.h" +#include "ndhc.h" + +static char clientid_path[PATH_MAX] = "/etc/ndhc"; + +void set_clientid_path(char *df) +{ + strnkcpy(clientid_path, df, sizeof clientid_path); +} + +static void fail_if_clientid_path_dne(void) +{ + if (strlen(clientid_path) == 0) { + log_error("clientid path is empty; it must be specified"); + exit(EXIT_FAILURE); + } + struct stat st; + if (stat(clientid_path, &st) < 0) { + log_error("failed to stat clientid path '%s': %s", + clientid_path, strerror(errno)); + exit(EXIT_FAILURE); + } + if (!S_ISDIR(st.st_mode)) { + log_error("clientid path '%s' does not specify a directory", + clientid_path); + exit(EXIT_FAILURE); + } +} + +static void get_duid_path(char *duidfile, size_t dlen) +{ + fail_if_clientid_path_dne(); + int splen = snprintf(duidfile, sizeof dlen, "%s/DUID", clientid_path); + if (splen < 0) { + log_line("%s: snprintf failed; return=%d", __func__, splen); + exit(EXIT_FAILURE); + } + if ((size_t)splen >= sizeof dlen) { + log_line("%s: snprintf dest buffer too small %d >= %u", + __func__, splen, sizeof dlen); + exit(EXIT_FAILURE); + } +} + +static void get_iaid_path(char *iaidfile, size_t ilen, uint8_t *hwaddr, + size_t hwaddrlen) +{ + fail_if_clientid_path_dne(); + if (hwaddrlen != 6) { + log_line("%s: Hardware address length=%u != 6 bytes", + __func__, hwaddrlen); + exit(EXIT_FAILURE); + } + int splen = snprintf + (iaidfile, sizeof ilen, + "%s/IAID-%.2hhx:%.2hhx:%.2hhx:%.2hhx:%.2hhx:%.2hhx", + clientid_path, hwaddr[0], hwaddr[1], hwaddr[2], + hwaddr[3], hwaddr[4], hwaddr[5]); + if (splen < 0) { + log_line("%s: snprintf failed; return=%d", __func__, splen); + exit(EXIT_FAILURE); + } + if ((size_t)splen >= sizeof ilen) { + log_line("%s: snprintf dest buffer too small %d >= %u", + __func__, splen, sizeof ilen); + exit(EXIT_FAILURE); + } +} + +static int open_duidfile_read(void) +{ + char duidfile[PATH_MAX]; + get_duid_path(duidfile, sizeof duidfile); + int fd = -1; + fd = open(duidfile, O_RDONLY, 0); + if (fd < 0) { + log_line("Failed to open duidfile '%s' for reading: %s", + duidfile, strerror(errno)); + } + return fd; +} + +static int open_duidfile_write(void) +{ + char duidfile[PATH_MAX]; + get_duid_path(duidfile, sizeof duidfile); + int fd = -1; + fd = open(duidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644); + if (fd < 0) { + log_line("Failed to open duidfile '%s' for writing: %s", + duidfile, strerror(errno)); + exit(EXIT_FAILURE); + } + return fd; +} + +static int open_iaidfile_read(uint8_t *hwaddr, size_t hwaddrlen) +{ + char iaidfile[PATH_MAX]; + get_iaid_path(iaidfile, sizeof iaidfile, hwaddr, hwaddrlen); + int fd = -1; + fd = open(iaidfile, O_RDONLY, 0); + if (fd < 0) { + log_line("Failed to open iaidfile '%s' for reading: %s", + iaidfile, strerror(errno)); + } + return fd; +} + +static int open_iaidfile_write(uint8_t *hwaddr, size_t hwaddrlen) +{ + char iaidfile[PATH_MAX]; + get_iaid_path(iaidfile, sizeof iaidfile, hwaddr, hwaddrlen); + int fd = -1; + fd = open(iaidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644); + if (fd < 0) { + log_line("Failed to open iaidfile '%s' for writing: %s", + iaidfile, strerror(errno)); + exit(EXIT_FAILURE); + } + return fd; +} + +// We use DUID-UUID (RFC6355) +// It is a 16-bit type=4 in network byte order followed by a 128-byte UUID. +// RFC6355 specifies a RFC4122 UUID, but I simply use a 128-byte random +// value, as the complexity of RFC4122 UUID generation is completely +// unwarranted for DHCPv4. +static size_t generate_duid(struct nk_random_state_u32 *s, char *dest, + size_t dlen) +{ + const size_t tlen = sizeof(uint16_t) + 4 * sizeof(uint32_t); + if (dlen < tlen) { + log_error("%s: dlen < %u", __func__, tlen); + exit(EXIT_FAILURE); + } + size_t off = 0; + + uint16_t typefield = htons(4); + memcpy(dest+off, &typefield, sizeof typefield); + off += sizeof typefield; + + for (size_t i = 0; i < 4; ++i) { + uint32_t r32 = nk_random_u32(s); + memcpy(dest+off, &r32, sizeof r32); + off += sizeof r32; + } + return dlen - off; +} + +// RFC6355 specifies the IAID as a 32-bit value that uniquely identifies +// a hardware link for a given host. +static size_t generate_iaid(struct nk_random_state_u32 *s, char *dest, + size_t dlen) +{ + if (dlen < sizeof(uint32_t)) { + log_error("%s: dlen < %u", __func__, sizeof(uint32_t)); + exit(EXIT_FAILURE); + } + size_t off = 0; + + uint32_t r32 = nk_random_u32(s); + memcpy(dest+off, &r32, sizeof r32); + off += sizeof r32; + return dlen - off; +} + +// Failures are all fatal. +void get_clientid(struct client_state_t *cs, struct client_config_t *cc) +{ + if (cc->clientid_len > 0) + return; + char iaid[sizeof cc->clientid]; + char duid[sizeof cc->clientid]; + size_t iaid_len; + size_t duid_len; + int fd = open_iaidfile_read(cc->arp, sizeof cc->arp); + if (fd < 0) { + iaid_len = generate_iaid(&cs->rnd32_state, iaid, sizeof iaid); + fd = open_iaidfile_write(cc->arp, sizeof cc->arp); + int r = safe_write(fd, iaid, iaid_len); + if (r < 0 || (size_t)r != iaid_len) { + log_error("%s: (%s) failed to write generated IAID.", + cc->interface, __func__); + exit(EXIT_FAILURE); + } + } else { + iaid_len = safe_read(fd, iaid, sizeof iaid); + } + close(fd); + + fd = open_duidfile_read(); + if (fd < 0) { + duid_len = generate_duid(&cs->rnd32_state, duid, sizeof duid); + fd = open_duidfile_write(); + int r = safe_write(fd, duid, duid_len); + if (r < 0 || (size_t)r != duid_len) { + log_error("%s: (%s) failed to write generated DUID.", + cc->interface, __func__); + exit(EXIT_FAILURE); + } + } else { + duid_len = safe_read(fd, duid, sizeof duid); + } + close(fd); + + const uint8_t cid_type = 255; + if (sizeof cid_type + iaid_len + duid_len > sizeof cc->clientid) { + log_error("%s: (%s) clientid length > %s", + cc->interface, __func__, sizeof cc->clientid); + exit(EXIT_FAILURE); + } + + uint8_t cid_len = 0; + memcpy(&cc->clientid + cid_len, &cid_type, sizeof cid_type); + cid_len += sizeof cid_type; + memcpy(&cc->clientid + cid_len, iaid, iaid_len); + cid_len += iaid_len; + memcpy(&cc->clientid + cid_len, duid, duid_len); + cid_len += duid_len; + cc->clientid_len = cid_len; +} diff --git a/ndhc/duiaid.h b/ndhc/duiaid.h new file mode 100644 index 0000000..002fa6a --- /dev/null +++ b/ndhc/duiaid.h @@ -0,0 +1,36 @@ +/* duiaid.h - DUID/IAID storage and generation for clientids + * + * Copyright (c) 2014 Nicholas J. Kain + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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 NJK_NDHC_DUIAID_H_ +#define NJK_NDHC_DUIAID_H_ + +#include "ndhc.h" + +void set_clientid_path(char *df); +void get_clientid(struct client_state_t *cs, struct client_config_t *cc); + +#endif /* NJK_NDHC_DUIAID_H_ */ diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index eb6f0dd..bd27883 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -61,7 +61,6 @@ #include "netlink.h" #include "leasefile.h" #include "ifset.h" - #include "log.h" #include "chroot.h" #include "cap.h" @@ -70,6 +69,7 @@ #include "io.h" #include "seccomp.h" #include "ifchd.h" +#include "duiaid.h" struct client_state_t cs = { .ifchWorking = 0, @@ -578,6 +578,8 @@ int main(int argc, char **argv) } } + nk_random_u32_init(&cs.rnd32_state); + if (getuid()) suicide("FATAL - I need to be started as root."); if (!strncmp(chroot_dir, "", sizeof chroot_dir)) @@ -588,6 +590,8 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } + get_clientid(&cs, &client_config); + switch (perform_ifup()) { case 1: cs.ifsPrevState = IFS_UP; @@ -603,11 +607,12 @@ int main(int argc, char **argv) if (ifch_pid == 0) { close(pToNdhcR); close(pToIfchW); + // Don't share the RNG state with the master process. + nk_random_u32_init(&cs.rnd32_state); ifch_main(); } else if (ifch_pid > 0) { close(pToIfchR); close(pToNdhcW); - nk_random_u32_init(&cs.rnd32_state); ndhc_main(); } else { log_line("FATAL - failed to fork ndhc-ifch: %s", strerror(errno)); diff --git a/ndhc/netlink.c b/ndhc/netlink.c index a875c63..ab81215 100644 --- a/ndhc/netlink.c +++ b/ndhc/netlink.c @@ -121,11 +121,6 @@ static int get_if_index_and_mac(const struct nlmsghdr *nlh, client_config.interface, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); memcpy(client_config.arp, mac, 6); - if (client_config.clientid_len < 2) { - client_config.clientid[0] = 1; // Ethernet MAC type - memcpy(&client_config.clientid + 1, mac, 6); - client_config.clientid_len = 7; - } return 1; } return 0;