diff options
Diffstat (limited to 'dhcp6_ctlclient.c')
-rw-r--r-- | dhcp6_ctlclient.c | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/dhcp6_ctlclient.c b/dhcp6_ctlclient.c new file mode 100644 index 0000000..5597c9e --- /dev/null +++ b/dhcp6_ctlclient.c @@ -0,0 +1,702 @@ +/* $KAME: dhcp6_ctlclient.c,v 1.5 2005/01/12 06:06:11 suz Exp $ */ + +/* + * Copyright (C) 2004 WIDE Project. + * 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. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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/types.h> +#include <sys/socket.h> +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +#include <netinet/in.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <netdb.h> +#include <err.h> + +#include <control.h> +#include <auth.h> +#include <base64.h> + +#define MD5_DIGESTLENGTH 16 +#define DEFAULT_SERVER_KEYFILE SYSCONFDIR "/dhcp6sctlkey" +#define DEFAULT_CLIENT_KEYFILE SYSCONFDIR "/dhcp6cctlkey" + +static char *ctladdr; +static char *ctlport; + +static enum { CTLCLIENT, CTLSERVER } ctltype = CTLCLIENT; + +static inline int put16 __P((char **, int *, u_int16_t)); +static inline int put32 __P((char **, int *, u_int32_t)); +static inline int putval __P((char **, int *, void *, size_t)); + +static int setup_auth __P((char *, struct keyinfo *, int *)); +static int make_command __P((int, char **, char **, size_t *, + struct keyinfo *, int)); +static int make_remove_command __P((int, char **, char **, int *)); +static int make_start_command __P((int, char **, char **, int *)); +static int make_stop_command __P((int, char **, char **, int *)); +static int make_binding_object __P((int, char **, char **, int *)); +static int make_interface_object __P((int, char **, char **, int *)); +static int make_ia_object __P((int, char **, char **, int *)); +static int parse_duid __P((char *, int *, char **, int *)); +static void usage __P((void)); + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int cc, ch, s, error, passed; + int Cflag = 0, Sflag = 0; + char *cbuf; + size_t clen; + struct addrinfo hints, *res0, *res; + int digestlen; + char *keyfile = NULL; + struct keyinfo key; + + while ((ch = getopt(argc, argv, "CSa:k:p:")) != -1) { + switch (ch) { + case 'C': + if (Sflag) + errx(1, "-C and -S are exclusive"); + Cflag = 1; + ctltype = CTLCLIENT; + break; + case 'S': + if (Cflag) + errx(1, "-C and -S are exclusive"); + Sflag = 1; + ctltype = CTLSERVER; + break; + case 'a': + ctladdr = optarg; + break; + case 'k': + keyfile = optarg; + break; + case 'p': + ctlport = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + switch (ctltype) { + case CTLCLIENT: + if (ctladdr == NULL) + ctladdr = DEFAULT_CLIENT_CONTROL_ADDR; + if (ctlport == NULL) + ctlport = DEFAULT_CLIENT_CONTROL_PORT; + if (keyfile == NULL) + keyfile = DEFAULT_CLIENT_KEYFILE; + break; + case CTLSERVER: + if (ctladdr == NULL) + ctladdr = DEFAULT_SERVER_CONTROL_ADDR; + if (ctlport == NULL) + ctlport = DEFAULT_SERVER_CONTROL_PORT; + if (keyfile == NULL) + keyfile = DEFAULT_SERVER_KEYFILE; + break; + } + + memset(&key, 0, sizeof(key)); + digestlen = 0; + if (setup_auth(keyfile, &key, &digestlen) != 0) + errx(1, "failed to setup message authentication"); + + if ((passed = make_command(argc, argv, &cbuf, &clen, + &key, digestlen)) < 0) { + errx(1, "failed to make command buffer"); + } + argc -= passed; + argv += passed; + if (argc != 0) + warnx("redundant command argument after \"%s\"", argv[0]); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + error = getaddrinfo(ctladdr, ctlport, &hints, &res0); + if (error != 0) + errx(1, "getaddrinfo failed: %s", gai_strerror(error)); + + s = -1; + for (res = res0; res != NULL; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (s < 0) { + warn("socket"); + continue; + } + if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { + warn("connect"); + s = -1; + continue; + } + break; + } + freeaddrinfo(res0); + if (s < 0) { + warnx("failed to connect to the %s", + ctltype == CTLCLIENT ? "client" : "server"); + exit(1); + } + + cc = write(s, cbuf, clen); + if (cc < 0) + err(1, "write command"); + if (cc != clen) + errx(1, "failed to send complete command"); + + close(s); + free(cbuf); + + exit(0); +} + +static int +setup_auth(keyfile, key, digestlenp) + char *keyfile; + struct keyinfo *key; + int *digestlenp; +{ + FILE *fp = NULL; + char line[1024], secret[1024]; + int secretlen; + + key->secret = NULL; + + /* Currently, we only support HMAC-MD5 for authentication. */ + *digestlenp = MD5_DIGESTLENGTH; + + if ((fp = fopen(keyfile, "r")) == NULL) { + warn("fopen: %s", keyfile); + return (-1); + } + if (fgets(line, sizeof(line), fp) == NULL && ferror(fp)) { + warn("fgets failed"); + goto fail; + } + if ((secretlen = base64_decodestring(line, secret, sizeof(secret))) + < 0) { + warnx("failed to decode base64 string"); + goto fail; + } + if ((key->secret = malloc(secretlen)) == NULL) { + warn("setup_auth: malloc failed"); + goto fail; + } + key->secretlen = (size_t)secretlen; + memcpy(key->secret, secret, secretlen); + + fclose(fp); + + return (0); + + fail: + if (fp != NULL) + fclose(fp); + if (key->secret != NULL) + free(key->secret); + return (-1); +} + +static inline int +put16(bpp, lenp, val) + char **bpp; + int *lenp; + u_int16_t val; +{ + char *bp = *bpp; + int len = *lenp; + + if (len < sizeof(val)) + return (-1); + + val = htons(val); + memcpy(bp, &val, sizeof(val)); + bp += sizeof(val); + len -= sizeof(val); + + *bpp = bp; + *lenp = len; + + return (0); +} + +static inline int +put32(bpp, lenp, val) + char **bpp; + int *lenp; + u_int32_t val; +{ + char *bp = *bpp; + int len = *lenp; + + if (len < sizeof(val)) + return (-1); + + val = htonl(val); + memcpy(bp, &val, sizeof(val)); + bp += sizeof(val); + len -= sizeof(val); + + *bpp = bp; + *lenp = len; + + return (0); +} + +static inline int +putval(bpp, lenp, val, valsize) + char **bpp; + int *lenp; + void *val; + size_t valsize; +{ + char *bp = *bpp; + int len = *lenp; + + if (len < valsize) + return (-1); + + memcpy(bp, val, valsize); + bp += valsize; + len -= valsize; + + *bpp = bp; + *lenp = len; + + return (0); +} + +static int +make_command(argc, argv, bufp, lenp, key, authlen) + int argc; + char **argv, **bufp; + size_t *lenp; + struct keyinfo *key; + int authlen; +{ + struct dhcp6ctl ctl; + char commandbuf[4096]; /* XXX: ad-hoc value */ + char *bp, *buf, *mac; + int buflen, len; + int argc_passed = 0, passed; + time_t now; + + if (argc == 0) { + warnx("command is too short"); + return (-1); + } + + bp = commandbuf + sizeof(ctl) + authlen; + if (bp >= commandbuf + sizeof(commandbuf)) { + warnx("make_command: local buffer is too short"); + return (-1); + } + buflen = sizeof(commandbuf) - sizeof(ctl); + + memset(&ctl, 0, sizeof(ctl)); + ctl.version = htons(DHCP6CTL_VERSION); + + if (strcmp(argv[0], "reload") == 0) + ctl.command = htons(DHCP6CTL_COMMAND_RELOAD); + else if (strcmp(argv[0], "remove") == 0) { + if (ctltype != CTLSERVER) { + warnx("remove command is only for server"); + return (-1); + } + if ((passed = make_remove_command(argc - 1, argv + 1, + &bp, &buflen)) < 0) { + return (-1); + } + argc_passed += passed; + ctl.command = htons(DHCP6CTL_COMMAND_REMOVE); + } else if (strcmp(argv[0], "start") == 0) { + if ((passed = make_start_command(argc - 1, argv + 1, + &bp, &buflen)) < 0) { + return (-1); + } + argc_passed += passed; + ctl.command = htons(DHCP6CTL_COMMAND_START); + } else if (strcmp(argv[0], "stop") == 0) { + if ((passed = make_stop_command(argc - 1, argv + 1, + &bp, &buflen)) < 0) { + return (-1); + } + argc_passed += passed; + ctl.command = htons(DHCP6CTL_COMMAND_STOP); + } else { + warnx("unknown command: %s", argv[0]); + return (-1); + } + + len = bp - commandbuf; + ctl.len = htons(len - sizeof(ctl)); + + if ((now = time(NULL)) < 0) { + warn("failed to get current time"); + return (-1); + } + ctl.timestamp = htonl((u_int32_t)now); + + memcpy(commandbuf, &ctl, sizeof(ctl)); + + mac = commandbuf + sizeof(ctl); + memset(mac, 0, authlen); + if (dhcp6_calc_mac(commandbuf, len, DHCP6CTL_AUTHPROTO_UNDEF, + DHCP6CTL_AUTHALG_HMACMD5, sizeof(ctl), key) != 0) { + warnx("failed to calculate MAC"); + return (-1); + } + + if ((buf = malloc(len)) == NULL) { + warn("memory allocation failed"); + return (-1); + } + memcpy(buf, commandbuf, len); + + *lenp = len; + *bufp = buf; + + argc_passed++; + + return (argc_passed); +} + +static int +make_remove_command(argc, argv, bpp, lenp) + int argc, *lenp; + char **argv, **bpp; +{ + int argc_passed = 0, passed; + + if (argc == 0) { + warnx("make_remove_command: command is too short"); + return (-1); + } + + if (strcmp(argv[0], "binding") == 0) { + if (put32(bpp, lenp, DHCP6CTL_BINDING)) + goto fail; + if ((passed = make_binding_object(argc - 1, argv + 1, + bpp, lenp)) < 0) { + return (-1); + } + argc_passed += passed; + } else { + warnx("remove target not supported: %s", argv[0]); + return (-1); + } + + argc_passed++; + return (argc_passed); + + fail: + warnx("make_remove_command failed"); + return (-1); +} + +static int +make_start_command(argc, argv, bpp, lenp) + int argc, *lenp; + char **argv, **bpp; +{ + int argc_passed = 0, passed; + + if (argc == 0) { + warnx("make_remove_command: command is too short"); + return (-1); + } + + if (ctltype != CTLCLIENT) { + warnx("client-only command is specified for a server"); + return (-1); + } + + if (strcmp(argv[0], "interface") == 0) { + if (put32(bpp, lenp, DHCP6CTL_INTERFACE)) + goto fail; + if ((passed = make_interface_object(argc - 1, argv + 1, + bpp, lenp)) < 0) { + return (-1); + } + argc_passed += passed; + } else { + warnx("start target not supported: %s", argv[0]); + return (-1); + } + + argc_passed++; + return (argc_passed); + + fail: + warnx("make_start_command failed"); + return (-1); +} + +static int +make_stop_command(argc, argv, bpp, lenp) + int argc, *lenp; + char **argv, **bpp; +{ + int argc_passed = 0, passed; + + if (argc == 0) + return (0); + + if (ctltype != CTLCLIENT) { + warnx("client-only command is specified for a server"); + return (-1); + } + + if (strcmp(argv[0], "interface") == 0) { + if (put32(bpp, lenp, DHCP6CTL_INTERFACE)) + goto fail; + if ((passed = make_interface_object(argc - 1, argv + 1, + bpp, lenp)) < 0) { + return (-1); + } + argc_passed += passed; + } else { + warnx("stop target not supported: %s", argv[0]); + return (-1); + } + + argc_passed++; + return (argc_passed); + + fail: + warnx("make_stop_command failed"); + return (-1); +} + +static int +make_interface_object(argc, argv, bpp, lenp) + int argc, *lenp; + char **argv, **bpp; +{ + int iflen; + int argc_passed = 0; + + if (argc == 0) { + warnx("make_interface_object: interface not specified"); + return (-1); + } + argc_passed++; + + iflen = strlen(argv[0]) + 1; + if (put32(bpp, lenp, (u_int32_t)iflen)) + goto fail; + if (putval(bpp, lenp, argv[0], strlen(argv[0]) + 1)) + goto fail; + + return (argc_passed); + + fail: + warnx("make_interface_object: failed"); + return (-1); +} + +static int +make_binding_object(argc, argv, bpp, lenp) + int argc, *lenp; + char **argv, **bpp; +{ + int argc_passed = 0, passed; + + if (argc == 0) { + /* or allow this as "all"? */ + warnx("make_binding_object: command is too short"); + return (-1); + } + + if (strcmp(argv[0], "IA") == 0) { + if (put32(bpp, lenp, DHCP6CTL_BINDING_IA)) + goto fail; + if ((passed = make_ia_object(argc - 1, argv + 1, + bpp, lenp)) < 0) { + return (-1); + } + argc_passed += passed; + } else { + warn("unknown binding type: %s", argv[0]); + return (-1); + } + + argc_passed++; + return (argc_passed); + + fail: + warnx("make_binding_object: failed"); + return (-1); +} + +static int +make_ia_object(argc, argv, bpp, lenp) + int argc, *lenp; + char **argv, **bpp; +{ + struct dhcp6ctl_iaspec iaspec; + int duidlen, dummylen = 0; + int argc_passed = 0; + char *dummy = NULL; + + if (argc < 3) { + /* + * Right now, we require all three parameters of + * <IA type, IAID, DUID>. This should be more flexible in + * the future. + */ + warnx("command is too short for an IA spec"); + return (-1); + } + argc_passed += 3; + + memset(&iaspec, 0, sizeof(iaspec)); + + if (strcmp(argv[0], "IA_PD") == 0) + iaspec.type = htonl(DHCP6CTL_IA_PD); + else if (strcmp(argv[0], "IA_NA") == 0) + iaspec.type = htonl(DHCP6CTL_IA_NA); + else { + warnx("IA type not supported: %s", argv[0]); + return (-1); + } + + iaspec.id = htonl((u_int32_t)strtol(argv[1], NULL, 10)); + + if (parse_duid(argv[2], &duidlen, &dummy, &dummylen)) + goto fail; + iaspec.duidlen = htonl(duidlen); + + if (putval(bpp, lenp, &iaspec, sizeof(iaspec))) + goto fail; + + if (parse_duid(argv[2], &duidlen, bpp, lenp)) + goto fail; + + return (argc_passed); + + fail: + warnx("make_ia_object: failed"); + return (-1); +} + +static int +parse_duid(str, lenp, bufp, buflenp) + char *str; + int *lenp; + char **bufp; + int *buflenp; +{ + char *buf = *bufp; + char *cp, *bp; + int duidlen, slen, buflen; + unsigned int x; + + /* calculate DUID len */ + slen = strlen(str); + if (slen < 2) + goto bad; + duidlen = 1; + slen -= 2; + if ((slen % 3) != 0) + goto bad; + duidlen += (slen / 3); + if (duidlen > 128) { + warn("too long DUID (%d bytes)", duidlen); + return (-1); + } + + *lenp = duidlen; + if (buf == NULL) + return (0); + + buflen = *buflenp; + if (buflen < duidlen) + goto bad; + + for (cp = str, bp = buf; *cp != '\0';) { + if (bp - buf >= buflen) + goto bad; + + if (sscanf(cp, "%02x", &x) != 1) + goto bad; + *bp++ = x; + cp += 2; + + switch (*cp) { + case ':': + cp++; + break; + case '\0': + goto done; + default: + goto bad; + } + } + done: + *bufp = bp; + return (0); + + bad: + return (-1); +} + +static void +usage() +{ + fprintf(stderr, "usage: dhcp6ctl [-C|-S] [-a ctladdr] [-k keyfile] " + "[-p ctlport] command...\n"); + + exit(1); +} |