nvram defaults now country=SG, txpwr=auto
[tomato.git] / release / src / router / dhcpv6 / dhcp6_ctlclient.c
blob5597c9eb0bbe3cc62b7f4332e2a592c61c4b3d82
1 /* $KAME: dhcp6_ctlclient.c,v 1.5 2005/01/12 06:06:11 suz Exp $ */
3 /*
4 * Copyright (C) 2004 WIDE Project.
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the project nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #if TIME_WITH_SYS_TIME
34 # include <sys/time.h>
35 # include <time.h>
36 #else
37 # if HAVE_SYS_TIME_H
38 # include <sys/time.h>
39 # else
40 # include <time.h>
41 # endif
42 #endif
44 #include <netinet/in.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <stdio.h>
49 #include <string.h>
50 #include <netdb.h>
51 #include <err.h>
53 #include <control.h>
54 #include <auth.h>
55 #include <base64.h>
57 #define MD5_DIGESTLENGTH 16
58 #define DEFAULT_SERVER_KEYFILE SYSCONFDIR "/dhcp6sctlkey"
59 #define DEFAULT_CLIENT_KEYFILE SYSCONFDIR "/dhcp6cctlkey"
61 static char *ctladdr;
62 static char *ctlport;
64 static enum { CTLCLIENT, CTLSERVER } ctltype = CTLCLIENT;
66 static inline int put16 __P((char **, int *, u_int16_t));
67 static inline int put32 __P((char **, int *, u_int32_t));
68 static inline int putval __P((char **, int *, void *, size_t));
70 static int setup_auth __P((char *, struct keyinfo *, int *));
71 static int make_command __P((int, char **, char **, size_t *,
72 struct keyinfo *, int));
73 static int make_remove_command __P((int, char **, char **, int *));
74 static int make_start_command __P((int, char **, char **, int *));
75 static int make_stop_command __P((int, char **, char **, int *));
76 static int make_binding_object __P((int, char **, char **, int *));
77 static int make_interface_object __P((int, char **, char **, int *));
78 static int make_ia_object __P((int, char **, char **, int *));
79 static int parse_duid __P((char *, int *, char **, int *));
80 static void usage __P((void));
82 int
83 main(argc, argv)
84 int argc;
85 char *argv[];
87 int cc, ch, s, error, passed;
88 int Cflag = 0, Sflag = 0;
89 char *cbuf;
90 size_t clen;
91 struct addrinfo hints, *res0, *res;
92 int digestlen;
93 char *keyfile = NULL;
94 struct keyinfo key;
96 while ((ch = getopt(argc, argv, "CSa:k:p:")) != -1) {
97 switch (ch) {
98 case 'C':
99 if (Sflag)
100 errx(1, "-C and -S are exclusive");
101 Cflag = 1;
102 ctltype = CTLCLIENT;
103 break;
104 case 'S':
105 if (Cflag)
106 errx(1, "-C and -S are exclusive");
107 Sflag = 1;
108 ctltype = CTLSERVER;
109 break;
110 case 'a':
111 ctladdr = optarg;
112 break;
113 case 'k':
114 keyfile = optarg;
115 break;
116 case 'p':
117 ctlport = optarg;
118 break;
119 default:
120 usage();
123 argc -= optind;
124 argv += optind;
126 if (argc == 0)
127 usage();
129 switch (ctltype) {
130 case CTLCLIENT:
131 if (ctladdr == NULL)
132 ctladdr = DEFAULT_CLIENT_CONTROL_ADDR;
133 if (ctlport == NULL)
134 ctlport = DEFAULT_CLIENT_CONTROL_PORT;
135 if (keyfile == NULL)
136 keyfile = DEFAULT_CLIENT_KEYFILE;
137 break;
138 case CTLSERVER:
139 if (ctladdr == NULL)
140 ctladdr = DEFAULT_SERVER_CONTROL_ADDR;
141 if (ctlport == NULL)
142 ctlport = DEFAULT_SERVER_CONTROL_PORT;
143 if (keyfile == NULL)
144 keyfile = DEFAULT_SERVER_KEYFILE;
145 break;
148 memset(&key, 0, sizeof(key));
149 digestlen = 0;
150 if (setup_auth(keyfile, &key, &digestlen) != 0)
151 errx(1, "failed to setup message authentication");
153 if ((passed = make_command(argc, argv, &cbuf, &clen,
154 &key, digestlen)) < 0) {
155 errx(1, "failed to make command buffer");
157 argc -= passed;
158 argv += passed;
159 if (argc != 0)
160 warnx("redundant command argument after \"%s\"", argv[0]);
162 memset(&hints, 0, sizeof(hints));
163 hints.ai_family = AF_INET6;
164 hints.ai_socktype = SOCK_STREAM;
165 hints.ai_protocol = IPPROTO_TCP;
166 error = getaddrinfo(ctladdr, ctlport, &hints, &res0);
167 if (error != 0)
168 errx(1, "getaddrinfo failed: %s", gai_strerror(error));
170 s = -1;
171 for (res = res0; res != NULL; res = res->ai_next) {
172 s = socket(res->ai_family, res->ai_socktype,
173 res->ai_protocol);
174 if (s < 0) {
175 warn("socket");
176 continue;
178 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
179 warn("connect");
180 s = -1;
181 continue;
183 break;
185 freeaddrinfo(res0);
186 if (s < 0) {
187 warnx("failed to connect to the %s",
188 ctltype == CTLCLIENT ? "client" : "server");
189 exit(1);
192 cc = write(s, cbuf, clen);
193 if (cc < 0)
194 err(1, "write command");
195 if (cc != clen)
196 errx(1, "failed to send complete command");
198 close(s);
199 free(cbuf);
201 exit(0);
204 static int
205 setup_auth(keyfile, key, digestlenp)
206 char *keyfile;
207 struct keyinfo *key;
208 int *digestlenp;
210 FILE *fp = NULL;
211 char line[1024], secret[1024];
212 int secretlen;
214 key->secret = NULL;
216 /* Currently, we only support HMAC-MD5 for authentication. */
217 *digestlenp = MD5_DIGESTLENGTH;
219 if ((fp = fopen(keyfile, "r")) == NULL) {
220 warn("fopen: %s", keyfile);
221 return (-1);
223 if (fgets(line, sizeof(line), fp) == NULL && ferror(fp)) {
224 warn("fgets failed");
225 goto fail;
227 if ((secretlen = base64_decodestring(line, secret, sizeof(secret)))
228 < 0) {
229 warnx("failed to decode base64 string");
230 goto fail;
232 if ((key->secret = malloc(secretlen)) == NULL) {
233 warn("setup_auth: malloc failed");
234 goto fail;
236 key->secretlen = (size_t)secretlen;
237 memcpy(key->secret, secret, secretlen);
239 fclose(fp);
241 return (0);
243 fail:
244 if (fp != NULL)
245 fclose(fp);
246 if (key->secret != NULL)
247 free(key->secret);
248 return (-1);
251 static inline int
252 put16(bpp, lenp, val)
253 char **bpp;
254 int *lenp;
255 u_int16_t val;
257 char *bp = *bpp;
258 int len = *lenp;
260 if (len < sizeof(val))
261 return (-1);
263 val = htons(val);
264 memcpy(bp, &val, sizeof(val));
265 bp += sizeof(val);
266 len -= sizeof(val);
268 *bpp = bp;
269 *lenp = len;
271 return (0);
274 static inline int
275 put32(bpp, lenp, val)
276 char **bpp;
277 int *lenp;
278 u_int32_t val;
280 char *bp = *bpp;
281 int len = *lenp;
283 if (len < sizeof(val))
284 return (-1);
286 val = htonl(val);
287 memcpy(bp, &val, sizeof(val));
288 bp += sizeof(val);
289 len -= sizeof(val);
291 *bpp = bp;
292 *lenp = len;
294 return (0);
297 static inline int
298 putval(bpp, lenp, val, valsize)
299 char **bpp;
300 int *lenp;
301 void *val;
302 size_t valsize;
304 char *bp = *bpp;
305 int len = *lenp;
307 if (len < valsize)
308 return (-1);
310 memcpy(bp, val, valsize);
311 bp += valsize;
312 len -= valsize;
314 *bpp = bp;
315 *lenp = len;
317 return (0);
320 static int
321 make_command(argc, argv, bufp, lenp, key, authlen)
322 int argc;
323 char **argv, **bufp;
324 size_t *lenp;
325 struct keyinfo *key;
326 int authlen;
328 struct dhcp6ctl ctl;
329 char commandbuf[4096]; /* XXX: ad-hoc value */
330 char *bp, *buf, *mac;
331 int buflen, len;
332 int argc_passed = 0, passed;
333 time_t now;
335 if (argc == 0) {
336 warnx("command is too short");
337 return (-1);
340 bp = commandbuf + sizeof(ctl) + authlen;
341 if (bp >= commandbuf + sizeof(commandbuf)) {
342 warnx("make_command: local buffer is too short");
343 return (-1);
345 buflen = sizeof(commandbuf) - sizeof(ctl);
347 memset(&ctl, 0, sizeof(ctl));
348 ctl.version = htons(DHCP6CTL_VERSION);
350 if (strcmp(argv[0], "reload") == 0)
351 ctl.command = htons(DHCP6CTL_COMMAND_RELOAD);
352 else if (strcmp(argv[0], "remove") == 0) {
353 if (ctltype != CTLSERVER) {
354 warnx("remove command is only for server");
355 return (-1);
357 if ((passed = make_remove_command(argc - 1, argv + 1,
358 &bp, &buflen)) < 0) {
359 return (-1);
361 argc_passed += passed;
362 ctl.command = htons(DHCP6CTL_COMMAND_REMOVE);
363 } else if (strcmp(argv[0], "start") == 0) {
364 if ((passed = make_start_command(argc - 1, argv + 1,
365 &bp, &buflen)) < 0) {
366 return (-1);
368 argc_passed += passed;
369 ctl.command = htons(DHCP6CTL_COMMAND_START);
370 } else if (strcmp(argv[0], "stop") == 0) {
371 if ((passed = make_stop_command(argc - 1, argv + 1,
372 &bp, &buflen)) < 0) {
373 return (-1);
375 argc_passed += passed;
376 ctl.command = htons(DHCP6CTL_COMMAND_STOP);
377 } else {
378 warnx("unknown command: %s", argv[0]);
379 return (-1);
382 len = bp - commandbuf;
383 ctl.len = htons(len - sizeof(ctl));
385 if ((now = time(NULL)) < 0) {
386 warn("failed to get current time");
387 return (-1);
389 ctl.timestamp = htonl((u_int32_t)now);
391 memcpy(commandbuf, &ctl, sizeof(ctl));
393 mac = commandbuf + sizeof(ctl);
394 memset(mac, 0, authlen);
395 if (dhcp6_calc_mac(commandbuf, len, DHCP6CTL_AUTHPROTO_UNDEF,
396 DHCP6CTL_AUTHALG_HMACMD5, sizeof(ctl), key) != 0) {
397 warnx("failed to calculate MAC");
398 return (-1);
401 if ((buf = malloc(len)) == NULL) {
402 warn("memory allocation failed");
403 return (-1);
405 memcpy(buf, commandbuf, len);
407 *lenp = len;
408 *bufp = buf;
410 argc_passed++;
412 return (argc_passed);
415 static int
416 make_remove_command(argc, argv, bpp, lenp)
417 int argc, *lenp;
418 char **argv, **bpp;
420 int argc_passed = 0, passed;
422 if (argc == 0) {
423 warnx("make_remove_command: command is too short");
424 return (-1);
427 if (strcmp(argv[0], "binding") == 0) {
428 if (put32(bpp, lenp, DHCP6CTL_BINDING))
429 goto fail;
430 if ((passed = make_binding_object(argc - 1, argv + 1,
431 bpp, lenp)) < 0) {
432 return (-1);
434 argc_passed += passed;
435 } else {
436 warnx("remove target not supported: %s", argv[0]);
437 return (-1);
440 argc_passed++;
441 return (argc_passed);
443 fail:
444 warnx("make_remove_command failed");
445 return (-1);
448 static int
449 make_start_command(argc, argv, bpp, lenp)
450 int argc, *lenp;
451 char **argv, **bpp;
453 int argc_passed = 0, passed;
455 if (argc == 0) {
456 warnx("make_remove_command: command is too short");
457 return (-1);
460 if (ctltype != CTLCLIENT) {
461 warnx("client-only command is specified for a server");
462 return (-1);
465 if (strcmp(argv[0], "interface") == 0) {
466 if (put32(bpp, lenp, DHCP6CTL_INTERFACE))
467 goto fail;
468 if ((passed = make_interface_object(argc - 1, argv + 1,
469 bpp, lenp)) < 0) {
470 return (-1);
472 argc_passed += passed;
473 } else {
474 warnx("start target not supported: %s", argv[0]);
475 return (-1);
478 argc_passed++;
479 return (argc_passed);
481 fail:
482 warnx("make_start_command failed");
483 return (-1);
486 static int
487 make_stop_command(argc, argv, bpp, lenp)
488 int argc, *lenp;
489 char **argv, **bpp;
491 int argc_passed = 0, passed;
493 if (argc == 0)
494 return (0);
496 if (ctltype != CTLCLIENT) {
497 warnx("client-only command is specified for a server");
498 return (-1);
501 if (strcmp(argv[0], "interface") == 0) {
502 if (put32(bpp, lenp, DHCP6CTL_INTERFACE))
503 goto fail;
504 if ((passed = make_interface_object(argc - 1, argv + 1,
505 bpp, lenp)) < 0) {
506 return (-1);
508 argc_passed += passed;
509 } else {
510 warnx("stop target not supported: %s", argv[0]);
511 return (-1);
514 argc_passed++;
515 return (argc_passed);
517 fail:
518 warnx("make_stop_command failed");
519 return (-1);
522 static int
523 make_interface_object(argc, argv, bpp, lenp)
524 int argc, *lenp;
525 char **argv, **bpp;
527 int iflen;
528 int argc_passed = 0;
530 if (argc == 0) {
531 warnx("make_interface_object: interface not specified");
532 return (-1);
534 argc_passed++;
536 iflen = strlen(argv[0]) + 1;
537 if (put32(bpp, lenp, (u_int32_t)iflen))
538 goto fail;
539 if (putval(bpp, lenp, argv[0], strlen(argv[0]) + 1))
540 goto fail;
542 return (argc_passed);
544 fail:
545 warnx("make_interface_object: failed");
546 return (-1);
549 static int
550 make_binding_object(argc, argv, bpp, lenp)
551 int argc, *lenp;
552 char **argv, **bpp;
554 int argc_passed = 0, passed;
556 if (argc == 0) {
557 /* or allow this as "all"? */
558 warnx("make_binding_object: command is too short");
559 return (-1);
562 if (strcmp(argv[0], "IA") == 0) {
563 if (put32(bpp, lenp, DHCP6CTL_BINDING_IA))
564 goto fail;
565 if ((passed = make_ia_object(argc - 1, argv + 1,
566 bpp, lenp)) < 0) {
567 return (-1);
569 argc_passed += passed;
570 } else {
571 warn("unknown binding type: %s", argv[0]);
572 return (-1);
575 argc_passed++;
576 return (argc_passed);
578 fail:
579 warnx("make_binding_object: failed");
580 return (-1);
583 static int
584 make_ia_object(argc, argv, bpp, lenp)
585 int argc, *lenp;
586 char **argv, **bpp;
588 struct dhcp6ctl_iaspec iaspec;
589 int duidlen, dummylen = 0;
590 int argc_passed = 0;
591 char *dummy = NULL;
593 if (argc < 3) {
595 * Right now, we require all three parameters of
596 * <IA type, IAID, DUID>. This should be more flexible in
597 * the future.
599 warnx("command is too short for an IA spec");
600 return (-1);
602 argc_passed += 3;
604 memset(&iaspec, 0, sizeof(iaspec));
606 if (strcmp(argv[0], "IA_PD") == 0)
607 iaspec.type = htonl(DHCP6CTL_IA_PD);
608 else if (strcmp(argv[0], "IA_NA") == 0)
609 iaspec.type = htonl(DHCP6CTL_IA_NA);
610 else {
611 warnx("IA type not supported: %s", argv[0]);
612 return (-1);
615 iaspec.id = htonl((u_int32_t)strtol(argv[1], NULL, 10));
617 if (parse_duid(argv[2], &duidlen, &dummy, &dummylen))
618 goto fail;
619 iaspec.duidlen = htonl(duidlen);
621 if (putval(bpp, lenp, &iaspec, sizeof(iaspec)))
622 goto fail;
624 if (parse_duid(argv[2], &duidlen, bpp, lenp))
625 goto fail;
627 return (argc_passed);
629 fail:
630 warnx("make_ia_object: failed");
631 return (-1);
634 static int
635 parse_duid(str, lenp, bufp, buflenp)
636 char *str;
637 int *lenp;
638 char **bufp;
639 int *buflenp;
641 char *buf = *bufp;
642 char *cp, *bp;
643 int duidlen, slen, buflen;
644 unsigned int x;
646 /* calculate DUID len */
647 slen = strlen(str);
648 if (slen < 2)
649 goto bad;
650 duidlen = 1;
651 slen -= 2;
652 if ((slen % 3) != 0)
653 goto bad;
654 duidlen += (slen / 3);
655 if (duidlen > 128) {
656 warn("too long DUID (%d bytes)", duidlen);
657 return (-1);
660 *lenp = duidlen;
661 if (buf == NULL)
662 return (0);
664 buflen = *buflenp;
665 if (buflen < duidlen)
666 goto bad;
668 for (cp = str, bp = buf; *cp != '\0';) {
669 if (bp - buf >= buflen)
670 goto bad;
672 if (sscanf(cp, "%02x", &x) != 1)
673 goto bad;
674 *bp++ = x;
675 cp += 2;
677 switch (*cp) {
678 case ':':
679 cp++;
680 break;
681 case '\0':
682 goto done;
683 default:
684 goto bad;
687 done:
688 *bufp = bp;
689 return (0);
691 bad:
692 return (-1);
695 static void
696 usage()
698 fprintf(stderr, "usage: dhcp6ctl [-C|-S] [-a ctladdr] [-k keyfile] "
699 "[-p ctlport] command...\n");
701 exit(1);