/* $KAME: common.c,v 1.129 2005/09/16 11:30:13 suz Exp $ */ /* * Copyright (C) 1998 and 1999 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 #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #ifdef __KAME__ #include #ifdef __FreeBSD__ #include #endif #include #endif #ifdef __linux__ #include #endif #include #ifdef __sun__ #include #include #include #include #include #endif #ifdef __KAME__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "dhcp6.h" #include "config.h" #include "common.h" #include "timer.h" #ifdef __linux__ /* from /usr/include/linux/ipv6.h */ struct in6_ifreq { struct in6_addr ifr6_addr; uint32_t ifr6_prefixlen; unsigned int ifr6_ifindex; }; #endif #define MAXDNAME 255 int foreground; int debug_thresh; static int dhcp6_count_list(struct dhcp6_list *); static int in6_matchflags(struct sockaddr *, char *, int); static ssize_t dnsencode(const char *, char *, size_t); static char *dnsdecode(u_char **, u_char *, char *, size_t); static int copyout_option(char *, char *, struct dhcp6_listval *); static int copyin_option(int, struct dhcp6opt *, struct dhcp6opt *, struct dhcp6_list *); static int copy_option(uint16_t, uint16_t, void *, struct dhcp6opt **, struct dhcp6opt *, int *); static ssize_t gethwid(char *, int, const char *, uint16_t *); static char *sprint_uint64(char *, int, uint64_t); static char *sprint_auth(struct dhcp6_optinfo *); int rawop_count_list(head) struct rawop_list *head; { struct rawoption *op; int i; //d_printf(LOG_INFO, FNAME, "counting list at %p", (void*)head); for (i = 0, op = TAILQ_FIRST(head); op; op = TAILQ_NEXT(op, link)) { i++; } return (i); } void rawop_clear_list(head) struct rawop_list *head; { struct rawoption *op; //d_printf(LOG_INFO, FNAME, "clearing %d rawops at %p", rawop_count_list(head), (void*)head); while ((op = TAILQ_FIRST(head)) != NULL) { //d_printf(LOG_INFO, FNAME, " current op: %p link: %p", (void*)op, op->link); TAILQ_REMOVE(head, op, link); if (op->data != NULL) { d_printf(LOG_INFO, FNAME, " freeing op data at %p", (void*)op->data); free(op->data); } free(op); // Needed? yes } return; } int rawop_copy_list(dst, src) struct rawop_list *dst, *src; { struct rawoption *op, *newop; /* d_printf(LOG_INFO, FNAME, " copying rawop list %p to %p (%d ops)", (void*)src, (void*)dst, rawop_count_list(src)); */ for (op = TAILQ_FIRST(src); op; op = TAILQ_NEXT(op, link)) { newop = NULL; if ((newop = malloc(sizeof(*newop))) == NULL) { d_printf(LOG_ERR, FNAME, "failed to allocate memory for a new raw option"); goto fail; } memset(newop, 0, sizeof(*newop)); newop->opnum = op->opnum; newop->datalen = op->datalen; newop->data = NULL; /* copy data */ if ((newop->data = malloc(newop->datalen)) == NULL) { d_printf(LOG_ERR, FNAME, "failed to allocate memory for new raw option data"); goto fail; } memcpy(newop->data, op->data, newop->datalen); //d_printf(LOG_INFO, FNAME, " copied %d bytes of data at %p", newop->datalen, (void*)newop->data); TAILQ_INSERT_TAIL(dst, newop, link); } return (0); fail: rawop_clear_list(dst); return (-1); } void rawop_move_list(dst, src) struct rawop_list *dst, *src; { struct rawoption *op; /* d_printf(LOG_INFO, FNAME, " moving rawop list of %d from %p to %p", rawop_count_list(src), (void*)src, (void*)dst); */ while ((op = TAILQ_FIRST(src)) != NULL) { TAILQ_REMOVE(src, op, link); TAILQ_INSERT_TAIL(dst, op, link); } } int dhcp6_copy_list(struct dhcp6_list *dst, struct dhcp6_list *src) { struct dhcp6_listval *ent; for (ent = TAILQ_FIRST(src); ent; ent = TAILQ_NEXT(ent, link)) { if (dhcp6_add_listval(dst, ent->type, &ent->uv, &ent->sublist) == NULL) goto fail; } return (0); fail: dhcp6_clear_list(dst); return (-1); } void dhcp6_move_list(struct dhcp6_list *dst, struct dhcp6_list *src) { struct dhcp6_listval *v; while ((v = TAILQ_FIRST(src)) != NULL) { TAILQ_REMOVE(src, v, link); TAILQ_INSERT_TAIL(dst, v, link); } } void dhcp6_clear_list(struct dhcp6_list *head) { struct dhcp6_listval *v; while ((v = TAILQ_FIRST(head)) != NULL) { TAILQ_REMOVE(head, v, link); dhcp6_clear_listval(v); } return; } static int dhcp6_count_list(struct dhcp6_list *head) { struct dhcp6_listval *v; int i; for (i = 0, v = TAILQ_FIRST(head); v; v = TAILQ_NEXT(v, link)) i++; return (i); } void dhcp6_clear_listval(struct dhcp6_listval *lv) { dhcp6_clear_list(&lv->sublist); switch (lv->type) { case DHCP6_LISTVAL_VBUF: dhcp6_vbuf_free(&lv->val_vbuf); break; default: /* nothing to do */ break; } free(lv); } /* * Note: this function only searches for the first entry that matches * VAL. It also does not care about sublists. */ struct dhcp6_listval * dhcp6_find_listval(struct dhcp6_list *head, dhcp6_listval_type_t type, void *val, int option) { struct dhcp6_listval *lv; for (lv = TAILQ_FIRST(head); lv; lv = TAILQ_NEXT(lv, link)) { if (lv->type != type) continue; switch(type) { case DHCP6_LISTVAL_NUM: if (lv->val_num == *(int *)val) return (lv); break; case DHCP6_LISTVAL_STCODE: if (lv->val_num16 == *(uint16_t *)val) return (lv); break; case DHCP6_LISTVAL_ADDR6: if (IN6_ARE_ADDR_EQUAL(&lv->val_addr6, (struct in6_addr *)val)) { return (lv); } break; case DHCP6_LISTVAL_PREFIX6: if ((option & MATCHLIST_PREFIXLEN) && lv->val_prefix6.plen == ((struct dhcp6_prefix *)val)->plen) { return (lv); } else if (IN6_ARE_ADDR_EQUAL(&lv->val_prefix6.addr, &((struct dhcp6_prefix *)val)->addr) && lv->val_prefix6.plen == ((struct dhcp6_prefix *)val)->plen) { return (lv); } break; case DHCP6_LISTVAL_STATEFULADDR6: if (IN6_ARE_ADDR_EQUAL(&lv->val_statefuladdr6.addr, &((struct dhcp6_prefix *)val)->addr)) { return (lv); } break; case DHCP6_LISTVAL_IAPD: case DHCP6_LISTVAL_IANA: if (lv->val_ia.iaid == ((struct dhcp6_ia *)val)->iaid) { return (lv); } break; case DHCP6_LISTVAL_VBUF: if (dhcp6_vbuf_cmp(&lv->val_vbuf, (struct dhcp6_vbuf *)val) == 0) { return (lv); } break; } } return (NULL); } struct dhcp6_listval * dhcp6_add_listval(struct dhcp6_list *head, dhcp6_listval_type_t type, void *val, struct dhcp6_list *sublist) { struct dhcp6_listval *lv = NULL; if ((lv = malloc(sizeof(*lv))) == NULL) { d_printf(LOG_ERR, FNAME, "failed to allocate memory for list entry"); goto fail; } memset(lv, 0, sizeof(*lv)); lv->type = type; TAILQ_INIT(&lv->sublist); switch(type) { case DHCP6_LISTVAL_NUM: lv->val_num = *(int *)val; break; case DHCP6_LISTVAL_STCODE: lv->val_num16 = *(uint16_t *)val; break; case DHCP6_LISTVAL_ADDR6: lv->val_addr6 = *(struct in6_addr *)val; break; case DHCP6_LISTVAL_PREFIX6: lv->val_prefix6 = *(struct dhcp6_prefix *)val; break; case DHCP6_LISTVAL_STATEFULADDR6: lv->val_statefuladdr6 = *(struct dhcp6_statefuladdr *)val; break; case DHCP6_LISTVAL_IAPD: case DHCP6_LISTVAL_IANA: lv->val_ia = *(struct dhcp6_ia *)val; break; case DHCP6_LISTVAL_VBUF: if (dhcp6_vbuf_copy(&lv->val_vbuf, (struct dhcp6_vbuf *)val)) goto fail; break; default: d_printf(LOG_ERR, FNAME, "unexpected list value type (%d)", type); goto fail; } if (sublist && dhcp6_copy_list(&lv->sublist, sublist)) goto fail; TAILQ_INSERT_TAIL(head, lv, link); return (lv); fail: if (lv) free(lv); return (NULL); } int dhcp6_vbuf_copy(struct dhcp6_vbuf *dst, struct dhcp6_vbuf *src) { dst->dv_buf = malloc(src->dv_len); if (dst->dv_buf == NULL) return (-1); dst->dv_len = src->dv_len; memcpy(dst->dv_buf, src->dv_buf, dst->dv_len); return (0); } void dhcp6_vbuf_free(struct dhcp6_vbuf *vbuf) { free(vbuf->dv_buf); vbuf->dv_len = 0; vbuf->dv_buf = NULL; } int dhcp6_vbuf_cmp(struct dhcp6_vbuf *vb1, struct dhcp6_vbuf *vb2) { if (vb1->dv_len != vb2->dv_len) return (vb1->dv_len - vb2->dv_len); return (memcmp(vb1->dv_buf, vb2->dv_buf, vb1->dv_len)); } static int dhcp6_get_addr(int optlen, void *cp, dhcp6_listval_type_t type, struct dhcp6_list *list) { char *val; if (optlen % sizeof(struct in6_addr) || optlen == 0) { d_printf(LOG_INFO, FNAME, "malformed DHCP option: type %d, len %d", type, optlen); return -1; } for (val = (char *)cp; val < (char *)cp + optlen; val += sizeof(struct in6_addr)) { struct in6_addr valaddr; memcpy(&valaddr, val, sizeof(valaddr)); if (dhcp6_find_listval(list, DHCP6_LISTVAL_ADDR6, &valaddr, 0)) { d_printf(LOG_INFO, FNAME, "duplicated %s address (%s)", dhcp6optstr(type), in6addr2str(&valaddr, 0)); continue; } if (dhcp6_add_listval(list, DHCP6_LISTVAL_ADDR6, &valaddr, NULL) == NULL) { d_printf(LOG_ERR, FNAME, "failed to copy %s address", dhcp6optstr(type)); return -1; } } return 0; } static int dhcp6_set_addr(type, list, p, optep, len) dhcp6_listval_type_t type; struct dhcp6_list *list; struct dhcp6opt **p, *optep; int *len; { struct in6_addr *in6; struct dhcp6_listval *d; int optlen; if (TAILQ_EMPTY(list)) return 0; optlen = dhcp6_count_list(list) * sizeof(struct in6_addr); if ((in6 = malloc(optlen)) == NULL) { d_printf(LOG_ERR, FNAME, "memory allocation failed for %s options", dhcp6optstr(type)); return -1; } for (d = TAILQ_FIRST(list); d; d = TAILQ_NEXT(d, link), in6++) memcpy(in6, &d->val_addr6, sizeof(*in6)); if (copy_option(type, optlen, (char *)in6, p, optep, len) != 0) { free(in6); return -1; } free(in6); return 0; } static int dhcp6_get_domain(optlen, cp, type, list) int optlen; void *cp; dhcp6_listval_type_t type; struct dhcp6_list *list; { char *val; val = (char *)cp; while (val < (char *)cp + optlen) { struct dhcp6_vbuf vb; char name[MAXDNAME + 1]; if (dnsdecode((u_char **)(void *)&val, (u_char *)((char *)cp + optlen), name, sizeof(name)) == NULL) { d_printf(LOG_INFO, FNAME, "failed to " "decode a %s domain name", dhcp6optstr(type)); d_printf(LOG_INFO, FNAME, "malformed DHCP option: type %d, len %d", type, optlen); return -1; } vb.dv_len = strlen(name) + 1; vb.dv_buf = name; if (dhcp6_add_listval(list, DHCP6_LISTVAL_VBUF, &vb, NULL) == NULL) { d_printf(LOG_ERR, FNAME, "failed to " "copy a %s domain name", dhcp6optstr(type)); return -1; } } return 0; } static int dhcp6_set_domain(type, list, p, optep, len) dhcp6_listval_type_t type; struct dhcp6_list *list; struct dhcp6opt **p, *optep; int *len; { int optlen = 0; struct dhcp6_listval *d; char *tmpbuf; char name[MAXDNAME], *cp, *ep; if (TAILQ_EMPTY(list)) return 0; for (d = TAILQ_FIRST(list); d; d = TAILQ_NEXT(d, link)) optlen += (d->val_vbuf.dv_len + 1); if (optlen == 0) { return 0; } tmpbuf = NULL; if ((tmpbuf = malloc(optlen)) == NULL) { d_printf(LOG_ERR, FNAME, "memory allocation failed for " "%s domain options", dhcp6optstr(type)); return -1; } cp = tmpbuf; ep = cp + optlen; for (d = TAILQ_FIRST(list); d; d = TAILQ_NEXT(d, link)) { int nlen; nlen = dnsencode((const char *)d->val_vbuf.dv_buf, name, sizeof (name)); if (nlen < 0) { d_printf(LOG_ERR, FNAME, "failed to encode a %s domain name", dhcp6optstr(type)); free(tmpbuf); return -1; } if (ep - cp < nlen) { d_printf(LOG_ERR, FNAME, "buffer length for %s domain name is too short", dhcp6optstr(type)); free(tmpbuf); return -1; } memcpy(cp, name, nlen); cp += nlen; } if (copy_option(type, cp - tmpbuf, tmpbuf, p, optep, len) != 0) { free(tmpbuf); return -1; } free(tmpbuf); return 0; } struct dhcp6_event * dhcp6_create_event(ifp, state) struct dhcp6_if *ifp; int state; { struct dhcp6_event *ev; if ((ev = malloc(sizeof(*ev))) == NULL) { d_printf(LOG_ERR, FNAME, "failed to allocate memory for an event"); return (NULL); } memset(ev, 0, sizeof(*ev)); ev->ifp = ifp; ev->state = state; TAILQ_INIT(&ev->data_list); return (ev); } void dhcp6_remove_event(ev) struct dhcp6_event *ev; { struct dhcp6_serverinfo *sp, *sp_next; d_printf(LOG_DEBUG, FNAME, "removing an event on %s, state=%s", ev->ifp->ifname, dhcp6_event_statestr(ev)); dhcp6_remove_evdata(ev); duidfree(&ev->serverid); if (ev->timer) dhcp6_remove_timer(&ev->timer); TAILQ_REMOVE(&ev->ifp->event_list, ev, link); for (sp = ev->servers; sp; sp = sp_next) { sp_next = sp->next; d_printf(LOG_DEBUG, FNAME, "removing server (ID: %s)", duidstr(&sp->optinfo.serverID)); dhcp6_clear_options(&sp->optinfo); if (sp->authparam != NULL) free(sp->authparam); free(sp); } if (ev->authparam != NULL) free(ev->authparam); free(ev); } void dhcp6_remove_evdata(ev) struct dhcp6_event *ev; { struct dhcp6_eventdata *evd; while ((evd = TAILQ_FIRST(&ev->data_list)) != NULL) { TAILQ_REMOVE(&ev->data_list, evd, link); if (evd->destructor) (*evd->destructor)(evd); free(evd); } } struct authparam * new_authparam(proto, alg, rdm) int proto, alg, rdm; { struct authparam *authparam; if ((authparam = malloc(sizeof(*authparam))) == NULL) return (NULL); memset(authparam, 0, sizeof(*authparam)); authparam->authproto = proto; authparam->authalgorithm = alg; authparam->authrdm = rdm; authparam->key = NULL; authparam->flags |= AUTHPARAM_FLAGS_NOPREVRD; authparam->prevrd = 0; return (authparam); } struct authparam * copy_authparam(authparam) struct authparam *authparam; { struct authparam *dst; if ((dst = malloc(sizeof(*dst))) == NULL) return (NULL); memcpy(dst, authparam, sizeof(*dst)); return (dst); } /* * Home-brew function of a 64-bit version of ntohl. * XXX: is there any standard for this? */ #if (BYTE_ORDER == LITTLE_ENDIAN) static __inline uint64_t ntohq(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 ntohq(x) (x) #endif int dhcp6_auth_replaycheck(method, prev, current) int method; uint64_t prev, current; { char bufprev[] = "ffff ffff ffff ffff"; char bufcurrent[] = "ffff ffff ffff ffff"; if (method != DHCP6_AUTHRDM_MONOCOUNTER) { d_printf(LOG_ERR, FNAME, "unsupported replay detection " "method (%d)", method); return (-1); } (void)sprint_uint64(bufprev, sizeof(bufprev), prev); (void)sprint_uint64(bufcurrent, sizeof(bufcurrent), current); d_printf(LOG_DEBUG, FNAME, "previous: %s, current: %s", bufprev, bufcurrent); prev = ntohq(prev); current = ntohq(current); /* * we call the singular point guilty, since we cannot guess * whether the serial number is increasing or not. */ if (prev == (current ^ 0x8000000000000000ULL)) { d_printf(LOG_INFO, FNAME, "detected a singular point"); return (1); } return (((int64_t)(current - prev) > 0) ? 0 : 1); } int getifaddr(addr, ifnam, prefix, plen, strong, ignoreflags) struct in6_addr *addr; char *ifnam; struct in6_addr *prefix; int plen; int strong; /* if strong host model is required or not */ int ignoreflags; { struct ifaddrs *ifap, *ifa; struct sockaddr_in6 sin6; int error = -1; if (getifaddrs(&ifap) != 0) { d_printf(LOG_WARNING, FNAME, "getifaddrs failed: %s", strerror(errno)); return (-1); } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { int s1, s2; if (strong && strcmp(ifnam, ifa->ifa_name) != 0) continue; /* in any case, ignore interfaces in different scope zones. */ if ((s1 = in6_addrscopebyif(prefix, ifnam)) < 0 || (s2 = in6_addrscopebyif(prefix, ifa->ifa_name)) < 0 || s1 != s2) continue; if (ifa->ifa_addr->sa_family != AF_INET6) continue; #ifdef HAVE_SA_LEN if (ifa->ifa_addr->sa_len > sizeof(sin6)) continue; #endif if (in6_matchflags(ifa->ifa_addr, ifa->ifa_name, ignoreflags)) continue; memcpy(&sin6, ifa->ifa_addr, sysdep_sa_len(ifa->ifa_addr)); #ifdef __KAME__ if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) { sin6.sin6_addr.s6_addr[2] = 0; sin6.sin6_addr.s6_addr[3] = 0; } #endif if (plen % 8 == 0) { if (memcmp(&sin6.sin6_addr, prefix, plen / 8) != 0) continue; } else { struct in6_addr a, m; int i; memcpy(&a, &sin6.sin6_addr, sizeof(sin6.sin6_addr)); memset(&m, 0, sizeof(m)); memset(&m, 0xff, plen / 8); m.s6_addr[plen / 8] = (0xff00 >> (plen % 8)) & 0xff; for (i = 0; i < (int)sizeof(a); i++) a.s6_addr[i] &= m.s6_addr[i]; if (memcmp(&a, prefix, plen / 8) != 0 || a.s6_addr[plen / 8] != (prefix->s6_addr[plen / 8] & m.s6_addr[plen / 8])) continue; } memcpy(addr, &sin6.sin6_addr, sizeof(sin6.sin6_addr)); #ifdef __KAME__ if (IN6_IS_ADDR_LINKLOCAL(addr)) addr->s6_addr[2] = addr->s6_addr[3] = 0; #endif error = 0; break; } freeifaddrs(ifap); return (error); } int getifidfromaddr(addr, ifidp) struct in6_addr *addr; unsigned int *ifidp; { struct ifaddrs *ifap, *ifa; struct sockaddr_in6 *sa6; unsigned int ifid; int retval = -1; if (getifaddrs(&ifap) != 0) { d_printf(LOG_WARNING, FNAME, "getifaddrs failed: %s", strerror(errno)); return (-1); } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; sa6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr; if (IN6_ARE_ADDR_EQUAL(addr, &sa6->sin6_addr)) break; } if (ifa != NULL) { if ((ifid = if_nametoindex(ifa->ifa_name)) == 0) { d_printf(LOG_ERR, FNAME, "if_nametoindex failed for %s", ifa->ifa_name); goto end; } retval = 0; *ifidp = ifid; } end: freeifaddrs(ifap); return (retval); } int in6_addrscopebyif(addr, ifnam) struct in6_addr *addr; char *ifnam; { u_int ifindex; if ((ifindex = if_nametoindex(ifnam)) == 0) return (-1); if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr)) return (ifindex); if (IN6_IS_ADDR_SITELOCAL(addr) || IN6_IS_ADDR_MC_SITELOCAL(addr)) return (1); /* XXX */ if (IN6_IS_ADDR_MC_ORGLOCAL(addr)) return (1); /* XXX */ return (1); /* treat it as global */ } int transmit_sa(s, sa, buf, len) int s; struct sockaddr *sa; char *buf; size_t len; { ssize_t error; error = sendto(s, buf, len, 0, sa, sysdep_sa_len(sa)); return (error != (ssize_t)len) ? -1 : 0; } long random_between(x, y) long x; long y; { long ratio; ratio = 1 << 16; while ((y - x) * ratio < (y - x)) ratio = ratio / 2; return (x + ((y - x) * (ratio - 1) / random() & (ratio - 1))); } int prefix6_mask(in6, plen) struct in6_addr *in6; int plen; { struct sockaddr_in6 mask6; int i; if (sa6_plen2mask(&mask6, plen)) return (-1); for (i = 0; i < 16; i++) in6->s6_addr[i] &= mask6.sin6_addr.s6_addr[i]; return (0); } int sa6_plen2mask(sa6, plen) struct sockaddr_in6 *sa6; int plen; { u_char *cp; if (plen < 0 || plen > 128) return (-1); memset(sa6, 0, sizeof(*sa6)); sa6->sin6_family = AF_INET6; #ifdef HAVE_SA_LEN sa6->sin6_len = sizeof(*sa6); #endif for (cp = (u_char *)&sa6->sin6_addr; plen > 7; plen -= 8) *cp++ = 0xff; *cp = 0xff << (8 - plen); return (0); } char * addr2str(sa) struct sockaddr *sa; { static char addrbuf[8][NI_MAXHOST]; static int round = 0; char *cp; round = (round + 1) & 7; cp = addrbuf[round]; getnameinfo(sa, sysdep_sa_len(sa), cp, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); return (cp); } char * in6addr2str(in6, scopeid) struct in6_addr *in6; int scopeid; { struct sockaddr_in6 sa6; memset(&sa6, 0, sizeof(sa6)); sa6.sin6_family = AF_INET6; #ifdef HAVE_SA_LEN sa6.sin6_len = sizeof(sa6); #endif sa6.sin6_addr = *in6; sa6.sin6_scope_id = scopeid; return (addr2str((struct sockaddr *)&sa6)); } /* return IPv6 address scope type. caller assumes that smaller is narrower. */ int in6_scope(addr) struct in6_addr *addr; { int scope; if (addr->s6_addr[0] == 0xfe) { scope = addr->s6_addr[1] & 0xc0; switch (scope) { case 0x80: return (2); /* link-local */ break; case 0xc0: return (5); /* site-local */ break; default: return (14); /* global: just in case */ break; } } /* multicast scope. just return the scope field */ if (addr->s6_addr[0] == 0xff) return (addr->s6_addr[1] & 0x0f); if (bcmp(&in6addr_loopback, addr, sizeof(addr) - 1) == 0) { if (addr->s6_addr[15] == 1) /* loopback */ return (1); if (addr->s6_addr[15] == 0) /* unspecified */ return (0); /* XXX: good value? */ } return (14); /* global */ } static int in6_matchflags(addr, ifnam, flags) struct sockaddr *addr; char *ifnam; int flags; { #ifdef __KAME__ int s; struct in6_ifreq ifr6; if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { warn("in6_matchflags: socket(DGRAM6)"); return (-1); } memset(&ifr6, 0, sizeof(ifr6)); strncpy(ifr6.ifr_name, ifnam, sizeof(ifr6.ifr_name)); ifr6.ifr_addr = *(struct sockaddr_in6 *)(void *)addr; if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) < 0) { warn("in6_matchflags: ioctl(SIOCGIFAFLAG_IN6, %s)", addr2str(addr)); close(s); return (-1); } close(s); return (ifr6.ifr_ifru.ifru_flags6 & flags); #else return (0); #endif } int get_duid(idfile, duid) const char *idfile; struct duid *duid; { FILE *fp = NULL; uint16_t len = 0, hwtype; struct dhcp6opt_duid_type1 *dp; /* we only support the type1 DUID */ char tmpbuf[256]; /* DUID should be no more than 256 bytes */ if ((fp = fopen(idfile, "r")) == NULL && errno != ENOENT) d_printf(LOG_NOTICE, FNAME, "failed to open DUID file: %s", idfile); if (fp) { /* decode length */ if (fread(&len, sizeof(len), 1, fp) != 1) { d_printf(LOG_ERR, FNAME, "DUID file corrupted"); goto fail; } } else { int l; if ((l = gethwid(tmpbuf, sizeof(tmpbuf), NULL, &hwtype)) < 0) { d_printf(LOG_INFO, FNAME, "failed to get a hardware address"); goto fail; } len = l + sizeof(struct dhcp6opt_duid_type1); } memset(duid, 0, sizeof(*duid)); duid->duid_len = len; if ((duid->duid_id = (char *)malloc(len)) == NULL) { d_printf(LOG_ERR, FNAME, "failed to allocate memory"); goto fail; } /* copy (and fill) the ID */ if (fp) { if (fread(duid->duid_id, len, 1, fp) != 1) { d_printf(LOG_ERR, FNAME, "DUID file corrupted"); goto fail; } d_printf(LOG_DEBUG, FNAME, "extracted an existing DUID from %s: %s", idfile, duidstr(duid)); } else { uint64_t t64; dp = (struct dhcp6opt_duid_type1 *)duid->duid_id; dp->dh6_duid1_type = htons(1); /* type 1 */ dp->dh6_duid1_hwtype = htons(hwtype); /* time is Jan 1, 2000 (UTC), modulo 2^32 */ t64 = (uint64_t)(time(NULL) - 946684800); dp->dh6_duid1_time = htonl((u_long)(t64 & 0xffffffff)); memcpy((void *)(dp + 1), tmpbuf, (len - sizeof(*dp))); d_printf(LOG_DEBUG, FNAME, "generated a new DUID: %s", duidstr(duid)); } /* save the (new) ID to the file for next time */ if (!fp) { if ((fp = fopen(idfile, "w+")) == NULL) { d_printf(LOG_ERR, FNAME, "failed to open DUID file %s for save", idfile); goto fail; } if ((fwrite(&len, sizeof(len), 1, fp)) != 1) { d_printf(LOG_ERR, FNAME, "failed to save DUID"); goto fail; } if ((fwrite(duid->duid_id, len, 1, fp)) != 1) { d_printf(LOG_ERR, FNAME, "failed to save DUID"); goto fail; } d_printf(LOG_DEBUG, FNAME, "saved generated DUID to %s", idfile); } if (fp) fclose(fp); return (0); fail: if (fp) fclose(fp); if (duid->duid_id) { free(duid->duid_id); duid->duid_id = NULL; /* for safety */ } return (-1); } #ifdef __sun__ struct hwparms { char *buf; uint16_t *hwtypep; ssize_t retval; }; static ssize_t getifhwaddr(const char *ifname, char *buf, uint16_t *hwtypep, int ppa) { int fd, flags; char fname[MAXPATHLEN], *cp; struct strbuf putctl; struct strbuf getctl; long getbuf[1024]; dl_info_req_t dlir; dl_phys_addr_req_t dlpar; dl_phys_addr_ack_t *dlpaa; d_printf(LOG_DEBUG, FNAME, "trying %s ppa %d", ifname, ppa); if (ifname[0] == '\0') return (-1); if (ppa >= 0 && !isdigit(ifname[strlen(ifname) - 1])) (void) snprintf(fname, sizeof (fname), "/dev/%s%d", ifname, ppa); else (void) snprintf(fname, sizeof (fname), "/dev/%s", ifname); getctl.maxlen = sizeof (getbuf); getctl.buf = (char *)getbuf; if ((fd = open(fname, O_RDWR)) == -1) { dl_attach_req_t dlar; cp = fname + strlen(fname) - 1; if (!isdigit(*cp)) return (-1); while (cp > fname) { if (!isdigit(*cp)) break; cp--; } if (cp == fname) return (-1); cp++; dlar.dl_ppa = atoi(cp); *cp = '\0'; if ((fd = open(fname, O_RDWR)) == -1) return (-1); dlar.dl_primitive = DL_ATTACH_REQ; putctl.len = sizeof (dlar); putctl.buf = (char *)&dlar; if (putmsg(fd, &putctl, NULL, 0) == -1) { (void) close(fd); return (-1); } flags = 0; if (getmsg(fd, &getctl, NULL, &flags) == -1) { (void) close(fd); return (-1); } if (getbuf[0] != DL_OK_ACK) { (void) close(fd); return (-1); } } dlir.dl_primitive = DL_INFO_REQ; putctl.len = sizeof (dlir); putctl.buf = (char *)&dlir; if (putmsg(fd, &putctl, NULL, 0) == -1) { (void) close(fd); return (-1); } flags = 0; if (getmsg(fd, &getctl, NULL, &flags) == -1) { (void) close(fd); return (-1); } if (getbuf[0] != DL_INFO_ACK) { (void) close(fd); return (-1); } switch (((dl_info_ack_t *)getbuf)->dl_mac_type) { case DL_CSMACD: case DL_ETHER: case DL_100VG: case DL_ETH_CSMA: case DL_100BT: *hwtypep = ARPHRD_ETHER; break; default: (void) close(fd); return (-1); } dlpar.dl_primitive = DL_PHYS_ADDR_REQ; dlpar.dl_addr_type = DL_CURR_PHYS_ADDR; putctl.len = sizeof (dlpar); putctl.buf = (char *)&dlpar; if (putmsg(fd, &putctl, NULL, 0) == -1) { (void) close(fd); return (-1); } flags = 0; if (getmsg(fd, &getctl, NULL, &flags) == -1) { (void) close(fd); return (-1); } if (getbuf[0] != DL_PHYS_ADDR_ACK) { (void) close(fd); return (-1); } dlpaa = (dl_phys_addr_ack_t *)getbuf; if (dlpaa->dl_addr_length != 6) { (void) close(fd); return (-1); } (void) memcpy(buf, (char *)getbuf + dlpaa->dl_addr_offset, dlpaa->dl_addr_length); return (dlpaa->dl_addr_length); } static int devfs_handler(di_node_t node, di_minor_t minor, void *arg) { struct hwparms *parms = arg; parms->retval = getifhwaddr(di_minor_name(minor), parms->buf, parms->hwtypep, di_instance(node)); return (parms->retval == -1 ? DI_WALK_CONTINUE : DI_WALK_TERMINATE); } #endif static ssize_t gethwid(buf, len, ifname, hwtypep) char *buf; int len; const char *ifname; uint16_t *hwtypep; { struct ifaddrs *ifa, *ifap; #ifdef __KAME__ struct sockaddr_dl *sdl; #endif #ifdef __linux__ struct sockaddr_ll *sll; #endif ssize_t l; #ifdef __sun__ if (ifname == NULL) { di_node_t root; struct hwparms parms; if ((root = di_init("/", DINFOSUBTREE | DINFOMINOR | DINFOPROP)) == DI_NODE_NIL) { d_printf(LOG_INFO, FNAME, "di_init failed"); return (-1); } parms.buf = buf; parms.hwtypep = hwtypep; parms.retval = -1; (void) di_walk_minor(root, DDI_NT_NET, DI_CHECK_ALIAS, &parms, devfs_handler); di_fini(root); return (parms.retval); } else { return (getifhwaddr(ifname, buf, hwtypep, -1)); } #endif if (getifaddrs(&ifap) < 0) return (-1); for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifname && strcmp(ifa->ifa_name, ifname) != 0) continue; if (ifa->ifa_addr == NULL) continue; #ifdef __KAME__ if (ifa->ifa_addr->sa_family != AF_LINK) continue; sdl = (struct sockaddr_dl *)(void *)ifa->ifa_addr; if (len < 2 + sdl->sdl_alen) goto fail; /* * translate interface type to hardware type based on * http://www.iana.org/assignments/arp-parameters */ switch(sdl->sdl_type) { case IFT_ETHER: #ifdef IFT_IEEE80211 case IFT_IEEE80211: #endif *hwtypep = ARPHRD_ETHER; break; default: continue; /* XXX */ } d_printf(LOG_DEBUG, FNAME, "found an interface %s for DUID", ifa->ifa_name); memcpy(buf, LLADDR(sdl), sdl->sdl_alen); l = sdl->sdl_alen; /* sdl will soon be freed */ #endif #ifdef __linux__ if (ifa->ifa_addr->sa_family != AF_PACKET) continue; sll = (struct sockaddr_ll *)ifa->ifa_addr; if (sll->sll_hatype != ARPHRD_ETHER) continue; *hwtypep = ARPHRD_ETHER; d_printf(LOG_DEBUG, FNAME, "found an interface %s for DUID", ifa->ifa_name); memcpy(buf, sll->sll_addr, sll->sll_halen); l = sll->sll_halen; /* sll will soon be freed */ #endif freeifaddrs(ifap); return (l); } fail: freeifaddrs(ifap); return (-1); } void dhcp6_init_options(optinfo) struct dhcp6_optinfo *optinfo; { memset(optinfo, 0, sizeof(*optinfo)); optinfo->pref = DH6OPT_PREF_UNDEF; optinfo->elapsed_time = DH6OPT_ELAPSED_TIME_UNDEF; optinfo->refreshtime = DH6OPT_REFRESHTIME_UNDEF; TAILQ_INIT(&optinfo->iapd_list); TAILQ_INIT(&optinfo->iana_list); TAILQ_INIT(&optinfo->reqopt_list); TAILQ_INIT(&optinfo->stcode_list); TAILQ_INIT(&optinfo->sip_list); TAILQ_INIT(&optinfo->sipname_list); TAILQ_INIT(&optinfo->dns_list); TAILQ_INIT(&optinfo->dnsname_list); TAILQ_INIT(&optinfo->ntp_list); TAILQ_INIT(&optinfo->prefix_list); TAILQ_INIT(&optinfo->nis_list); TAILQ_INIT(&optinfo->nisname_list); TAILQ_INIT(&optinfo->nisp_list); TAILQ_INIT(&optinfo->nispname_list); TAILQ_INIT(&optinfo->bcmcs_list); TAILQ_INIT(&optinfo->bcmcsname_list); TAILQ_INIT(&optinfo->rawops); optinfo->authproto = DHCP6_AUTHPROTO_UNDEF; optinfo->authalgorithm = DHCP6_AUTHALG_UNDEF; optinfo->authrdm = DHCP6_AUTHRDM_UNDEF; } void dhcp6_clear_options(optinfo) struct dhcp6_optinfo *optinfo; { switch (optinfo->authproto) { case DHCP6_AUTHPROTO_DELAYED: if (optinfo->delayedauth_realmval != NULL) { free(optinfo->delayedauth_realmval); } break; } duidfree(&optinfo->clientID); duidfree(&optinfo->serverID); dhcp6_clear_list(&optinfo->iapd_list); dhcp6_clear_list(&optinfo->iana_list); dhcp6_clear_list(&optinfo->reqopt_list); dhcp6_clear_list(&optinfo->stcode_list); dhcp6_clear_list(&optinfo->sip_list); dhcp6_clear_list(&optinfo->sipname_list); dhcp6_clear_list(&optinfo->dns_list); dhcp6_clear_list(&optinfo->dnsname_list); dhcp6_clear_list(&optinfo->ntp_list); dhcp6_clear_list(&optinfo->prefix_list); dhcp6_clear_list(&optinfo->nis_list); dhcp6_clear_list(&optinfo->nisname_list); dhcp6_clear_list(&optinfo->nisp_list); dhcp6_clear_list(&optinfo->nispname_list); dhcp6_clear_list(&optinfo->bcmcs_list); dhcp6_clear_list(&optinfo->bcmcsname_list); if (optinfo->relaymsg_msg != NULL) free(optinfo->relaymsg_msg); if (optinfo->ifidopt_id != NULL) free(optinfo->ifidopt_id); rawop_clear_list(&optinfo->rawops); dhcp6_init_options(optinfo); } int dhcp6_copy_options(dst, src) struct dhcp6_optinfo *dst, *src; { if (duidcpy(&dst->clientID, &src->clientID)) goto fail; if (duidcpy(&dst->serverID, &src->serverID)) goto fail; dst->rapidcommit = src->rapidcommit; if (dhcp6_copy_list(&dst->iapd_list, &src->iapd_list)) goto fail; if (dhcp6_copy_list(&dst->iana_list, &src->iana_list)) goto fail; if (dhcp6_copy_list(&dst->reqopt_list, &src->reqopt_list)) goto fail; if (dhcp6_copy_list(&dst->stcode_list, &src->stcode_list)) goto fail; if (dhcp6_copy_list(&dst->sip_list, &src->sip_list)) goto fail; if (dhcp6_copy_list(&dst->sipname_list, &src->sipname_list)) goto fail; if (dhcp6_copy_list(&dst->dns_list, &src->dns_list)) goto fail; if (dhcp6_copy_list(&dst->dnsname_list, &src->dnsname_list)) goto fail; if (dhcp6_copy_list(&dst->ntp_list, &src->ntp_list)) goto fail; if (dhcp6_copy_list(&dst->prefix_list, &src->prefix_list)) goto fail; if (dhcp6_copy_list(&dst->nis_list, &src->nis_list)) goto fail; if (dhcp6_copy_list(&dst->nisname_list, &src->nisname_list)) goto fail; if (dhcp6_copy_list(&dst->nisp_list, &src->nisp_list)) goto fail; if (dhcp6_copy_list(&dst->nispname_list, &src->nispname_list)) goto fail; if (dhcp6_copy_list(&dst->bcmcs_list, &src->bcmcs_list)) goto fail; if (dhcp6_copy_list(&dst->bcmcsname_list, &src->bcmcsname_list)) goto fail; dst->elapsed_time = src->elapsed_time; dst->refreshtime = src->refreshtime; dst->pref = src->pref; rawop_copy_list(&dst->rawops, &src->rawops); if (src->relaymsg_msg != NULL) { if ((dst->relaymsg_msg = malloc(src->relaymsg_len)) == NULL) goto fail; dst->relaymsg_len = src->relaymsg_len; memcpy(dst->relaymsg_msg, src->relaymsg_msg, src->relaymsg_len); } if (src->ifidopt_id != NULL) { if ((dst->ifidopt_id = malloc(src->ifidopt_len)) == NULL) goto fail; dst->ifidopt_len = src->ifidopt_len; memcpy(dst->ifidopt_id, src->ifidopt_id, src->ifidopt_len); } dst->authflags = src->authflags; dst->authproto = src->authproto; dst->authalgorithm = src->authalgorithm; dst->authrdm = src->authrdm; dst->authrd = src->authrd; switch (src->authproto) { case DHCP6_AUTHPROTO_DELAYED: dst->delayedauth_keyid = src->delayedauth_keyid; dst->delayedauth_offset = src->delayedauth_offset; dst->delayedauth_realmlen = src->delayedauth_realmlen; if (src->delayedauth_realmval != NULL) { if ((dst->delayedauth_realmval = malloc(src->delayedauth_realmlen)) == NULL) { goto fail; } memcpy(dst->delayedauth_realmval, src->delayedauth_realmval, src->delayedauth_realmlen); } break; case DHCP6_AUTHPROTO_RECONFIG: dst->reconfigauth_type = src->reconfigauth_type; dst->reconfigauth_offset = src->reconfigauth_offset; memcpy(dst->reconfigauth_val, src->reconfigauth_val, sizeof(dst->reconfigauth_val)); break; } return (0); fail: /* cleanup temporary resources */ dhcp6_clear_options(dst); return (-1); } int dhcp6_get_options(p, ep, optinfo) struct dhcp6opt *p, *ep; struct dhcp6_optinfo *optinfo; { struct dhcp6opt *np, opth; int i, opt, optlen, reqopts, num; uint16_t num16; char *bp, *cp, *val; uint16_t val16; uint32_t val32; struct dhcp6opt_ia optia; struct dhcp6_ia ia; struct dhcp6_list sublist; int authinfolen; bp = (char *)p; for (; p + 1 <= ep; p = np) { struct duid duid0; /* * get the option header. XXX: since there is no guarantee * about the header alignment, we need to make a local copy. */ memcpy(&opth, p, sizeof(opth)); optlen = ntohs(opth.dh6opt_len); opt = ntohs(opth.dh6opt_type); cp = (char *)(p + 1); np = (struct dhcp6opt *)(cp + optlen); d_printf(LOG_DEBUG, FNAME, "get DHCP option %s, len %d", dhcp6optstr(opt), optlen); /* option length field overrun */ if (np > ep) { d_printf(LOG_INFO, FNAME, "malformed DHCP options"); goto fail; } switch (opt) { case DH6OPT_CLIENTID: if (optlen == 0) goto malformed; duid0.duid_len = optlen; duid0.duid_id = cp; d_printf(LOG_DEBUG, "", " DUID: %s", duidstr(&duid0)); if (duidcpy(&optinfo->clientID, &duid0)) { d_printf(LOG_ERR, FNAME, "failed to copy DUID"); goto fail; } break; case DH6OPT_SERVERID: if (optlen == 0) goto malformed; duid0.duid_len = optlen; duid0.duid_id = cp; d_printf(LOG_DEBUG, "", " DUID: %s", duidstr(&duid0)); if (duidcpy(&optinfo->serverID, &duid0)) { d_printf(LOG_ERR, FNAME, "failed to copy DUID"); goto fail; } break; case DH6OPT_STATUS_CODE: if (optlen < (int)sizeof(uint16_t)) goto malformed; memcpy(&val16, cp, sizeof(val16)); num16 = ntohs(val16); d_printf(LOG_DEBUG, "", " status code: %s", dhcp6_stcodestr(num16)); /* need to check duplication? */ if (dhcp6_add_listval(&optinfo->stcode_list, DHCP6_LISTVAL_STCODE, &num16, NULL) == NULL) { d_printf(LOG_ERR, FNAME, "failed to copy " "status code"); goto fail; } break; case DH6OPT_ORO: if ((optlen % 2) != 0 || optlen == 0) goto malformed; reqopts = optlen / 2; for (i = 0, val = cp; i < reqopts; i++, val += sizeof(uint16_t)) { uint16_t opttype; memcpy(&opttype, val, sizeof(uint16_t)); num = (int)ntohs(opttype); d_printf(LOG_DEBUG, "", " requested option: %s", dhcp6optstr(num)); if (dhcp6_find_listval(&optinfo->reqopt_list, DHCP6_LISTVAL_NUM, &num, 0)) { d_printf(LOG_INFO, FNAME, "duplicated " "option type (%s)", dhcp6optstr(opttype)); goto nextoption; } if (dhcp6_add_listval(&optinfo->reqopt_list, DHCP6_LISTVAL_NUM, &num, NULL) == NULL) { d_printf(LOG_ERR, FNAME, "failed to copy requested option"); goto fail; } nextoption: ; } break; case DH6OPT_PREFERENCE: if (optlen != 1) goto malformed; d_printf(LOG_DEBUG, "", " preference: %d", (int)*(u_char *)cp); if (optinfo->pref != DH6OPT_PREF_UNDEF) { d_printf(LOG_INFO, FNAME, "duplicated preference option"); } else optinfo->pref = (int)*(u_char *)cp; break; case DH6OPT_ELAPSED_TIME: if (optlen != 2) goto malformed; memcpy(&val16, cp, sizeof(val16)); val16 = ntohs(val16); d_printf(LOG_DEBUG, "", " elapsed time: %lu", (uint32_t)val16); if (optinfo->elapsed_time != DH6OPT_ELAPSED_TIME_UNDEF) { d_printf(LOG_INFO, FNAME, "duplicated elapsed time option"); } else optinfo->elapsed_time = val16; break; case DH6OPT_RELAY_MSG: if ((optinfo->relaymsg_msg = malloc(optlen)) == NULL) goto fail; memcpy(optinfo->relaymsg_msg, cp, optlen); optinfo->relaymsg_len = optlen; break; case DH6OPT_AUTH: if (optlen < (int)sizeof(struct dhcp6opt_auth) - 4) goto malformed; /* * Any DHCP message that includes more than one * authentication option MUST be discarded. * [RFC3315 Section 21.4.2] */ if (optinfo->authproto != DHCP6_AUTHPROTO_UNDEF) { d_printf(LOG_INFO, FNAME, "found more than one " "authentication option"); goto fail; } optinfo->authproto = *cp++; optinfo->authalgorithm = *cp++; optinfo->authrdm = *cp++; memcpy(&optinfo->authrd, cp, sizeof(optinfo->authrd)); cp += sizeof(optinfo->authrd); d_printf(LOG_DEBUG, "", " %s", sprint_auth(optinfo)); authinfolen = optlen - (sizeof(struct dhcp6opt_auth) - 4); switch (optinfo->authproto) { case DHCP6_AUTHPROTO_DELAYED: if (authinfolen == 0) { optinfo->authflags |= DHCP6OPT_AUTHFLAG_NOINFO; break; } /* XXX: should we reject an empty realm? */ if (authinfolen < (int)sizeof(optinfo->delayedauth_keyid) + 16) { goto malformed; } optinfo->delayedauth_realmlen = authinfolen - (sizeof(optinfo->delayedauth_keyid) + 16); optinfo->delayedauth_realmval = malloc(optinfo->delayedauth_realmlen); if (optinfo->delayedauth_realmval == NULL) { d_printf(LOG_WARNING, FNAME, "failed " "allocate memory for auth realm"); goto fail; } memcpy(optinfo->delayedauth_realmval, cp, optinfo->delayedauth_realmlen); cp += optinfo->delayedauth_realmlen; memcpy(&optinfo->delayedauth_keyid, cp, sizeof(optinfo->delayedauth_keyid)); optinfo->delayedauth_keyid = ntohl(optinfo->delayedauth_keyid); cp += sizeof(optinfo->delayedauth_keyid); optinfo->delayedauth_offset = cp - bp; cp += 16; d_printf(LOG_DEBUG, "", " auth key ID: %x, " "offset=%d, realmlen=%d", optinfo->delayedauth_keyid, optinfo->delayedauth_offset, optinfo->delayedauth_realmlen); break; case DHCP6_AUTHPROTO_RECONFIG: d_printf(LOG_DEBUG, FNAME, " ignoring not implemented authentication protocol: reconfig"); break; /* XXX */ case 0: // Discard auth d_printf(LOG_DEBUG, FNAME, " Discarding null authentication"); optinfo->authproto = DHCP6_AUTHPROTO_UNDEF; optinfo->authalgorithm = DHCP6_AUTHALG_UNDEF; optinfo->authrdm = DHCP6_AUTHRDM_UNDEF; break; default: d_printf(LOG_INFO, FNAME, "unsupported authentication protocol: %d", optinfo->authproto); goto fail; } break; case DH6OPT_RAPID_COMMIT: if (optlen != 0) goto malformed; optinfo->rapidcommit = 1; break; case DH6OPT_INTERFACE_ID: if ((optinfo->ifidopt_id = malloc(optlen)) == NULL) goto fail; memcpy(optinfo->ifidopt_id, cp, optlen); optinfo->ifidopt_len = optlen; break; case DH6OPT_SIP_SERVER_D: if (dhcp6_get_domain(optlen, cp, opt, &optinfo->sipname_list) == -1) goto fail; break; case DH6OPT_DNSNAME: if (dhcp6_get_domain(optlen, cp, opt, &optinfo->dnsname_list) == -1) goto fail; break; case DH6OPT_NIS_DOMAIN_NAME: if (dhcp6_get_domain(optlen, cp, opt, &optinfo->nisname_list) == -1) goto fail; break; case DH6OPT_NISP_DOMAIN_NAME: if (dhcp6_get_domain(optlen, cp, opt, &optinfo->nispname_list) == -1) goto fail; break; case DH6OPT_BCMCS_SERVER_D: if (dhcp6_get_domain(optlen, cp, opt, &optinfo->bcmcsname_list) == -1) goto fail; break; case DH6OPT_SIP_SERVER_A: if (dhcp6_get_addr(optlen, cp, opt, &optinfo->sip_list) == -1) goto fail; break; case DH6OPT_DNS: if (dhcp6_get_addr(optlen, cp, opt, &optinfo->dns_list) == -1) goto fail; break; case DH6OPT_NIS_SERVERS: if (dhcp6_get_addr(optlen, cp, opt, &optinfo->nis_list) == -1) goto fail; break; case DH6OPT_NISP_SERVERS: if (dhcp6_get_addr(optlen, cp, opt, &optinfo->nisp_list) == -1) goto fail; break; case DH6OPT_BCMCS_SERVER_A: if (dhcp6_get_addr(optlen, cp, opt, &optinfo->bcmcs_list) == -1) goto fail; break; case DH6OPT_NTP: if (dhcp6_get_addr(optlen, cp, opt, &optinfo->ntp_list) == -1) goto fail; break; case DH6OPT_IA_PD: if (optlen + sizeof(struct dhcp6opt) < sizeof(optia)) goto malformed; memcpy(&optia, p, sizeof(optia)); ia.iaid = ntohl(optia.dh6_ia_iaid); ia.t1 = ntohl(optia.dh6_ia_t1); ia.t2 = ntohl(optia.dh6_ia_t2); d_printf(LOG_DEBUG, "", " IA_PD: ID=%lu, T1=%lu, T2=%lu", ia.iaid, ia.t1, ia.t2); /* duplication check */ if (dhcp6_find_listval(&optinfo->iapd_list, DHCP6_LISTVAL_IAPD, &ia, 0)) { d_printf(LOG_INFO, FNAME, "duplicated IA_PD %lu", ia.iaid); break; /* ignore this IA_PD */ } /* take care of sub-options */ TAILQ_INIT(&sublist); if (copyin_option(opt, (struct dhcp6opt *)((char *)p + sizeof(optia)), (struct dhcp6opt *)(cp + optlen), &sublist)) { goto fail; } /* link this option set */ if (dhcp6_add_listval(&optinfo->iapd_list, DHCP6_LISTVAL_IAPD, &ia, &sublist) == NULL) { dhcp6_clear_list(&sublist); goto fail; } dhcp6_clear_list(&sublist); break; case DH6OPT_REFRESHTIME: if (optlen != 4) goto malformed; memcpy(&val32, cp, sizeof(val32)); val32 = ntohl(val32); d_printf(LOG_DEBUG, "", " information refresh time: %lu", val32); if (val32 < DHCP6_IRT_MINIMUM) { /* * A client MUST use the refresh time * IRT_MINIMUM if it receives the option with a * value less than IRT_MINIMUM. * [draft-ietf-dhc-lifetime-02.txt, * Section 3.2] */ d_printf(LOG_INFO, FNAME, "refresh time is too small (%d), adjusted", val32); val32 = DHCP6_IRT_MINIMUM; } if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) { d_printf(LOG_INFO, FNAME, "duplicated refresh time option"); } else optinfo->refreshtime = (int64_t)val32; break; case DH6OPT_IA_NA: if (optlen + sizeof(struct dhcp6opt) < sizeof(optia)) goto malformed; memcpy(&optia, p, sizeof(optia)); ia.iaid = ntohl(optia.dh6_ia_iaid); ia.t1 = ntohl(optia.dh6_ia_t1); ia.t2 = ntohl(optia.dh6_ia_t2); d_printf(LOG_DEBUG, "", " IA_NA: ID=%lu, T1=%lu, T2=%lu", ia.iaid, ia.t1, ia.t2); /* duplication check */ if (dhcp6_find_listval(&optinfo->iana_list, DHCP6_LISTVAL_IANA, &ia, 0)) { d_printf(LOG_INFO, FNAME, "duplicated IA_NA %lu", ia.iaid); break; /* ignore this IA_NA */ } /* take care of sub-options */ TAILQ_INIT(&sublist); if (copyin_option(opt, (struct dhcp6opt *)((char *)p + sizeof(optia)), (struct dhcp6opt *)(cp + optlen), &sublist)) { goto fail; } /* link this option set */ if (dhcp6_add_listval(&optinfo->iana_list, DHCP6_LISTVAL_IANA, &ia, &sublist) == NULL) { dhcp6_clear_list(&sublist); goto fail; } dhcp6_clear_list(&sublist); break; default: /* no option specific behavior */ d_printf(LOG_INFO, FNAME, "unknown or unexpected DHCP6 option %s, len %d", dhcp6optstr(opt), optlen); break; } } return (0); malformed: d_printf(LOG_INFO, FNAME, "malformed DHCP option: type %d, len %d", opt, optlen); fail: dhcp6_clear_options(optinfo); return (-1); } static char * dnsdecode(sp, ep, buf, bufsiz) u_char **sp; u_char *ep; char *buf; size_t bufsiz; { int i, l; u_char *cp; char tmpbuf[MAXDNAME + 1]; cp = *sp; *buf = '\0'; i = 0; /* XXX: appease gcc */ if (cp >= ep) return (NULL); while (cp < ep) { i = *cp; if (i == 0 || cp != *sp) { if (strlcat((char *)buf, ".", bufsiz) >= bufsiz) return (NULL); /* result overrun */ } if (i == 0) break; cp++; if (i > 0x3f) return (NULL); /* invalid label */ if (i > ep - cp) return (NULL); /* source overrun */ while (i-- > 0 && cp < ep) { if (!isprint(*cp)) /* we don't accept non-printables */ return (NULL); l = snprintf(tmpbuf, sizeof(tmpbuf), "%c" , *cp); if (l >= (int)sizeof(tmpbuf) || l < 0) return (NULL); if (strlcat(buf, tmpbuf, bufsiz) >= bufsiz) return (NULL); /* result overrun */ cp++; } } if (i != 0) return (NULL); /* not terminated */ cp++; *sp = cp; return (buf); } static int copyin_option(type, p, ep, list) int type; struct dhcp6opt *p, *ep; struct dhcp6_list *list; { int opt, optlen; char *cp; struct dhcp6opt *np, opth; struct dhcp6opt_stcode opt_stcode; struct dhcp6opt_ia_pd_prefix opt_iapd_prefix; struct dhcp6_prefix iapd_prefix; struct dhcp6opt_ia_addr opt_ia_addr; struct dhcp6_prefix ia_addr; struct dhcp6_list sublist; TAILQ_INIT(&sublist); for (; p + 1 <= ep; p = np) { memcpy(&opth, p, sizeof(opth)); optlen = ntohs(opth.dh6opt_len); opt = ntohs(opth.dh6opt_type); cp = (char *)(p + 1); np = (struct dhcp6opt *)(cp + optlen); d_printf(LOG_DEBUG, FNAME, "get DHCP option %s, len %d", dhcp6optstr(opt), optlen); if (np > ep) { d_printf(LOG_INFO, FNAME, "malformed DHCP option"); goto fail; } switch (opt) { case DH6OPT_IA_PD_PREFIX: /* check option context */ if (type != DH6OPT_IA_PD) { d_printf(LOG_INFO, FNAME, "%s is an invalid position for %s", dhcp6optstr(type), dhcp6optstr(opt)); goto fail; } /* check option length */ if (optlen + sizeof(opth) < sizeof(opt_iapd_prefix)) goto malformed; /* copy and convert option values */ memcpy(&opt_iapd_prefix, p, sizeof(opt_iapd_prefix)); if (opt_iapd_prefix.dh6_iapd_prefix_prefix_len > 128) { d_printf(LOG_INFO, FNAME, "invalid prefix length (%d)", opt_iapd_prefix.dh6_iapd_prefix_prefix_len); goto malformed; } iapd_prefix.pltime = ntohl(opt_iapd_prefix.dh6_iapd_prefix_preferred_time); iapd_prefix.vltime = ntohl(opt_iapd_prefix.dh6_iapd_prefix_valid_time); iapd_prefix.plen = opt_iapd_prefix.dh6_iapd_prefix_prefix_len; memcpy(&iapd_prefix.addr, &opt_iapd_prefix.dh6_iapd_prefix_prefix_addr, sizeof(iapd_prefix.addr)); /* clear padding bits in the prefix address */ prefix6_mask(&iapd_prefix.addr, iapd_prefix.plen); d_printf(LOG_DEBUG, FNAME, " IA_PD prefix: " "%s/%d pltime=%lu vltime=%lu", in6addr2str(&iapd_prefix.addr, 0), iapd_prefix.plen, iapd_prefix.pltime, iapd_prefix.vltime); if (dhcp6_find_listval(list, DHCP6_LISTVAL_PREFIX6, &iapd_prefix, 0)) { d_printf(LOG_INFO, FNAME, "duplicated IA_PD prefix " "%s/%d pltime=%lu vltime=%lu", in6addr2str(&iapd_prefix.addr, 0), iapd_prefix.plen, iapd_prefix.pltime, iapd_prefix.vltime); goto nextoption; } /* take care of sub-options */ TAILQ_INIT(&sublist); if (copyin_option(opt, (struct dhcp6opt *)((char *)p + sizeof(opt_iapd_prefix)), np, &sublist)) { goto fail; } if (dhcp6_add_listval(list, DHCP6_LISTVAL_PREFIX6, &iapd_prefix, &sublist) == NULL) { dhcp6_clear_list(&sublist); goto fail; } dhcp6_clear_list(&sublist); break; case DH6OPT_IAADDR: /* check option context */ if (type != DH6OPT_IA_NA) { d_printf(LOG_INFO, FNAME, "%s is an invalid position for %s", dhcp6optstr(type), dhcp6optstr(opt)); goto fail; } /* check option length */ if (optlen + sizeof(opth) < sizeof(opt_ia_addr)) goto malformed; /* copy and convert option values */ memcpy(&opt_ia_addr, p, sizeof(opt_ia_addr)); ia_addr.pltime = ntohl(opt_ia_addr.dh6_ia_addr_preferred_time); ia_addr.vltime = ntohl(opt_ia_addr.dh6_ia_addr_valid_time); memcpy(&ia_addr.addr, &opt_ia_addr.dh6_ia_addr_addr, sizeof(ia_addr.addr)); d_printf(LOG_DEBUG, FNAME, " IA_NA address: " "%s pltime=%lu vltime=%lu", in6addr2str(&ia_addr.addr, 0), ia_addr.pltime, ia_addr.vltime); if (dhcp6_find_listval(list, DHCP6_LISTVAL_STATEFULADDR6, &ia_addr, 0)) { d_printf(LOG_INFO, FNAME, "duplicated IA_NA address" "%s pltime=%lu vltime=%lu", in6addr2str(&ia_addr.addr, 0), ia_addr.pltime, ia_addr.vltime); goto nextoption; } /* take care of sub-options */ TAILQ_INIT(&sublist); if (copyin_option(opt, (struct dhcp6opt *)((char *)p + sizeof(opt_ia_addr)), np, &sublist)) { goto fail; } if (dhcp6_add_listval(list, DHCP6_LISTVAL_STATEFULADDR6, &ia_addr, &sublist) == NULL) { dhcp6_clear_list(&sublist); goto fail; } dhcp6_clear_list(&sublist); break; case DH6OPT_STATUS_CODE: /* check option context */ if (type != DH6OPT_IA_PD && type != DH6OPT_IA_PD_PREFIX && type != DH6OPT_IA_NA && type != DH6OPT_IAADDR) { d_printf(LOG_INFO, FNAME, "%s is an invalid position for %s", dhcp6optstr(type), dhcp6optstr(opt)); goto nextoption; /* or discard the message? */ } /* check option length */ if (optlen + sizeof(opth) < sizeof(opt_stcode)) goto malformed; /* copy and convert option values */ memcpy(&opt_stcode, p, sizeof(opt_stcode)); opt_stcode.dh6_stcode_code = ntohs(opt_stcode.dh6_stcode_code); d_printf(LOG_DEBUG, "", " status code: %s", dhcp6_stcodestr(opt_stcode.dh6_stcode_code)); /* duplication check */ if (dhcp6_find_listval(list, DHCP6_LISTVAL_STCODE, &opt_stcode.dh6_stcode_code, 0)) { d_printf(LOG_INFO, FNAME, "duplicated status code (%d)", opt_stcode.dh6_stcode_code); goto nextoption; } /* copy-in the code value */ if (dhcp6_add_listval(list, DHCP6_LISTVAL_STCODE, &opt_stcode.dh6_stcode_code, NULL) == NULL) goto fail; break; } nextoption: ; } return (0); malformed: d_printf(LOG_INFO, "", " malformed DHCP option: type %d", opt); fail: dhcp6_clear_list(&sublist); return (-1); } static char * sprint_uint64(buf, buflen, i64) char *buf; int buflen; uint64_t i64; { uint16_t rd0, rd1, rd2, rd3; uint16_t *ptr = (uint16_t *)(void *)&i64; rd0 = ntohs(*ptr++); rd1 = ntohs(*ptr++); rd2 = ntohs(*ptr++); rd3 = ntohs(*ptr); snprintf(buf, buflen, "%04x %04x %04x %04x", rd0, rd1, rd2, rd3); return (buf); } static char * sprint_auth(optinfo) struct dhcp6_optinfo *optinfo; { static char ret[1024]; /* XXX: thread unsafe */ const char *proto; char proto0[] = "unknown(255)"; const char *alg; char alg0[] = "unknown(255)"; const char *rdm; char rdm0[] = "unknown(255)"; char rd[] = "ffff ffff ffff ffff"; switch (optinfo->authproto) { case DHCP6_AUTHPROTO_DELAYED: proto = "delayed"; break; case DHCP6_AUTHPROTO_RECONFIG: proto = "reconfig"; break; default: snprintf(proto0, sizeof(proto0), "unknown(%d)", optinfo->authproto & 0xff); proto = proto0; break; } switch (optinfo->authalgorithm) { case DHCP6_AUTHALG_HMACMD5: alg = "HMAC-MD5"; break; default: snprintf(alg0, sizeof(alg0), "unknown(%d)", optinfo->authalgorithm & 0xff); alg = alg0; break; } switch (optinfo->authrdm) { case DHCP6_AUTHRDM_MONOCOUNTER: rdm = "mono counter"; break; default: snprintf(rdm0, sizeof(rdm0), "unknown(%d)", optinfo->authrdm); rdm = rdm0; } (void)sprint_uint64(rd, sizeof(rd), optinfo->authrd); snprintf(ret, sizeof(ret), "proto: %s, alg: %s, RDM: %s, RD: %s", proto, alg, rdm, rd); return (ret); } static int copy_option(uint16_t type, uint16_t len, void *val, struct dhcp6opt **optp, struct dhcp6opt *ep, int *totallenp) { struct dhcp6opt *opt = *optp, opth; if ((char *)ep - (char *)optp < (intptr_t)(len + sizeof(struct dhcp6opt))) { d_printf(LOG_INFO, FNAME, "option buffer short for %s", dhcp6optstr(type)); return (-1); } opth.dh6opt_type = htons(type); opth.dh6opt_len = htons(len); memcpy(opt, &opth, sizeof(opth)); if (len != 0) memcpy(opt + 1, val, len); *optp = (struct dhcp6opt *)((char *)(opt + 1) + len); *totallenp += sizeof(struct dhcp6opt) + len; d_printf(LOG_DEBUG, FNAME, "set %s (len %d)", dhcp6optstr(type), len); return (0); } int dhcp6_set_options(type, optbp, optep, optinfo) int type; struct dhcp6opt *optbp, *optep; struct dhcp6_optinfo *optinfo; { struct dhcp6opt *p = optbp; struct dhcp6_listval *stcode, *op; int len = 0, optlen; char *tmpbuf = NULL; struct rawoption *rawop; if (optinfo->clientID.duid_len) { if (copy_option(DH6OPT_CLIENTID, optinfo->clientID.duid_len, optinfo->clientID.duid_id, &p, optep, &len) != 0) { goto fail; } } if (optinfo->serverID.duid_len) { if (copy_option(DH6OPT_SERVERID, optinfo->serverID.duid_len, optinfo->serverID.duid_id, &p, optep, &len) != 0) { goto fail; } } for (op = TAILQ_FIRST(&optinfo->iana_list); op; op = TAILQ_NEXT(op, link)) { tmpbuf = NULL; if ((optlen = copyout_option(NULL, NULL, op)) < 0) { d_printf(LOG_INFO, FNAME, "failed to count option length"); goto fail; } if ((char *)optep - (char *)p < (intptr_t)optlen) { d_printf(LOG_INFO, FNAME, "short buffer"); goto fail; } if ((tmpbuf = malloc(optlen)) == NULL) { d_printf(LOG_NOTICE, FNAME, "memory allocation failed for IA_NA options"); goto fail; } if (copyout_option(tmpbuf, tmpbuf + optlen, op) < 0) { d_printf(LOG_ERR, FNAME, "failed to construct an IA_NA option"); goto fail; } memcpy(p, tmpbuf, optlen); free(tmpbuf); tmpbuf = NULL; p = (struct dhcp6opt *)((char *)p + optlen); len += optlen; } if (optinfo->rapidcommit) { if (copy_option(DH6OPT_RAPID_COMMIT, 0, NULL, &p, optep, &len) != 0) { goto fail; } } if (optinfo->pref != DH6OPT_PREF_UNDEF) { uint8_t p8 = (uint8_t)optinfo->pref; if (copy_option(DH6OPT_PREFERENCE, sizeof(p8), &p8, &p, optep, &len) != 0) { goto fail; } } if (optinfo->elapsed_time != DH6OPT_ELAPSED_TIME_UNDEF) { uint16_t p16 = (uint16_t)optinfo->elapsed_time; p16 = htons(p16); if (copy_option(DH6OPT_ELAPSED_TIME, sizeof(p16), &p16, &p, optep, &len) != 0) { goto fail; } } for (stcode = TAILQ_FIRST(&optinfo->stcode_list); stcode; stcode = TAILQ_NEXT(stcode, link)) { uint16_t code; code = htons(stcode->val_num16); if (copy_option(DH6OPT_STATUS_CODE, sizeof(code), &code, &p, optep, &len) != 0) { goto fail; } } if (!TAILQ_EMPTY(&optinfo->reqopt_list)) { struct dhcp6_listval *opt; uint16_t *valp, *valp0; int buflen; buflen = dhcp6_count_list(&optinfo->reqopt_list) * sizeof(uint16_t); if ((valp0 = valp = malloc(buflen)) == NULL) { d_printf(LOG_ERR, FNAME, "memory allocation failed for options"); goto fail; } optlen = 0; for (opt = TAILQ_FIRST(&optinfo->reqopt_list); opt; opt = TAILQ_NEXT(opt, link)) { /* * Information request option can only be specified * in information-request messages. * [draft-ietf-dhc-lifetime-02.txt, Section 3.2] */ if (opt->val_num == DH6OPT_REFRESHTIME && type != DH6_INFORM_REQ) { d_printf(LOG_DEBUG, FNAME, "refresh time option is not requested " "for %s", dhcp6msgstr(type)); } *valp = htons((uint16_t)opt->val_num); valp++; optlen += sizeof(uint16_t); } if (optlen > 0 && copy_option(DH6OPT_ORO, optlen, (void *)valp0, &p, optep, &len) != 0) { goto fail; } free(valp0); } if (dhcp6_set_domain(DH6OPT_SIP_SERVER_D, &optinfo->sipname_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_addr(DH6OPT_SIP_SERVER_A, &optinfo->sip_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_addr(DH6OPT_DNS, &optinfo->dns_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_domain(DH6OPT_DNSNAME, &optinfo->dnsname_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_addr(DH6OPT_NIS_SERVERS, &optinfo->nis_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_addr(DH6OPT_NISP_SERVERS, &optinfo->nisp_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_domain(DH6OPT_NIS_DOMAIN_NAME, &optinfo->nisname_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_domain(DH6OPT_NISP_DOMAIN_NAME, &optinfo->nispname_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_addr(DH6OPT_NTP, &optinfo->ntp_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_domain(DH6OPT_BCMCS_SERVER_D, &optinfo->bcmcsname_list, &p, optep, &len) != 0) goto fail; if (dhcp6_set_addr(DH6OPT_BCMCS_SERVER_A, &optinfo->bcmcs_list, &p, optep, &len) != 0) goto fail; for (op = TAILQ_FIRST(&optinfo->iapd_list); op; op = TAILQ_NEXT(op, link)) { tmpbuf = NULL; if ((optlen = copyout_option(NULL, NULL, op)) < 0) { d_printf(LOG_INFO, FNAME, "failed to count option length"); goto fail; } if ((char *)optep - (char *)p < (intptr_t)optlen) { d_printf(LOG_INFO, FNAME, "short buffer"); goto fail; } if ((tmpbuf = malloc(optlen)) == NULL) { d_printf(LOG_NOTICE, FNAME, "memory allocation failed for IA_PD options"); goto fail; } if (copyout_option(tmpbuf, tmpbuf + optlen, op) < 0) { d_printf(LOG_ERR, FNAME, "failed to construct an IA_PD option"); goto fail; } memcpy(p, tmpbuf, optlen); free(tmpbuf); tmpbuf = NULL; p = (struct dhcp6opt *)((char *)p + optlen); len += optlen; } if (optinfo->relaymsg_len) { if (copy_option(DH6OPT_RELAY_MSG, optinfo->relaymsg_len, optinfo->relaymsg_msg, &p, optep, &len) != 0) { goto fail; } } if (optinfo->ifidopt_id) { if (copy_option(DH6OPT_INTERFACE_ID, optinfo->ifidopt_len, optinfo->ifidopt_id, &p, optep, &len) != 0) { goto fail; } } if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) { uint32_t p32 = (uint32_t)optinfo->refreshtime; p32 = htonl(p32); if (copy_option(DH6OPT_REFRESHTIME, sizeof(p32), &p32, &p, optep, &len) != 0) { goto fail; } } for (rawop = TAILQ_FIRST(&optinfo->rawops); rawop; rawop = TAILQ_NEXT(rawop, link)) { d_printf(LOG_DEBUG, FNAME, " raw option %d length %d at %p", rawop->opnum, rawop->datalen, (void*)rawop); if (copy_option(rawop->opnum, rawop->datalen, rawop->data, &p, optep, &len) != 0) { goto fail; } } if (optinfo->authproto != DHCP6_AUTHPROTO_UNDEF) { struct dhcp6opt_auth *auth; int authlen; char *authinfo; authlen = sizeof(*auth); if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) { switch (optinfo->authproto) { case DHCP6_AUTHPROTO_DELAYED: /* Realm + key ID + HMAC-MD5 */ authlen += optinfo->delayedauth_realmlen + sizeof(optinfo->delayedauth_keyid) + 16; break; #ifdef notyet case DHCP6_AUTHPROTO_RECONFIG: /* type + key-or-HAMC */ authlen += 17; break; #endif default: d_printf(LOG_ERR, FNAME, "unexpected authentication protocol"); goto fail; } } if ((auth = malloc(authlen)) == NULL) { d_printf(LOG_WARNING, FNAME, "failed to allocate " "memory for authentication information"); goto fail; } memset(auth, 0, authlen); /* copy_option will take care of type and len later */ auth->dh6_auth_proto = (uint8_t)optinfo->authproto; auth->dh6_auth_alg = (uint8_t)optinfo->authalgorithm; auth->dh6_auth_rdm = (uint8_t)optinfo->authrdm; memcpy(auth->dh6_auth_rdinfo, &optinfo->authrd, sizeof(auth->dh6_auth_rdinfo)); if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) { uint32_t p32; switch (optinfo->authproto) { case DHCP6_AUTHPROTO_DELAYED: authinfo = (char *)(auth + 1); /* copy realm */ memcpy(authinfo, optinfo->delayedauth_realmval, optinfo->delayedauth_realmlen); authinfo += optinfo->delayedauth_realmlen; /* copy key ID (need memcpy for alignment) */ p32 = htonl(optinfo->delayedauth_keyid); memcpy(authinfo, &p32, sizeof(p32)); /* * Set the offset so that the caller can * calculate the HMAC. */ optinfo->delayedauth_offset = ((char *)p - (char *)optbp) + authlen - 16; d_printf(LOG_DEBUG, FNAME, "key ID %x, offset %d", optinfo->delayedauth_keyid, optinfo->delayedauth_offset); break; #ifdef notyet case DHCP6_AUTHPROTO_RECONFIG: #endif default: d_printf(LOG_ERR, FNAME, "unexpected authentication protocol"); free(auth); goto fail; } } if (copy_option(DH6OPT_AUTH, authlen - 4, &auth->dh6_auth_proto, &p, optep, &len) != 0) { goto fail; } free(auth); } return (len); fail: if (tmpbuf) free(tmpbuf); return (-1); } #undef COPY_OPTION static ssize_t dnsencode(name, buf, buflen) const char *name; char *buf; size_t buflen; { char *cp, *ep; const char *p, *q; int i; int namelen = strlen(name); cp = buf; ep = cp + buflen; /* if not certain about my name, return an empty buffer */ if (namelen == 0) return (0); p = name; while (cp < ep && p < name + namelen) { i = 0; for (q = p; q < name + namelen && *q && *q != '.'; q++) i++; /* result does not fit into buf */ if (cp + i + 1 >= ep) goto fail; /* * DNS label length restriction, RFC1035 page 8. * "i == 0" case is included here to avoid returning * 0-length label on "foo..bar". */ if (i <= 0 || i >= 64) goto fail; *cp++ = i; if (!isalpha(p[0]) || !isalnum(p[i - 1])) goto fail; while (i > 0) { if (!isalnum(*p) && *p != '-') goto fail; if (isupper(*p)) *cp++ = tolower(*p++); else *cp++ = *p++; i--; } p = q; if (p < name + namelen && *p == '.') p++; } /* termination */ if (cp + 1 >= ep) goto fail; *cp++ = '\0'; return (cp - buf); fail: return (-1); } /* * Construct a DHCPv6 option along with sub-options in the wire format. * If the packet buffer is NULL, just calculate the length of the option * (and sub-options) so that the caller can allocate a buffer to store the * option(s). * This function basically assumes that the caller prepares enough buffer to * store all the options. However, it also takes the buffer end and checks * the possibility of overrun for safety. */ static int copyout_option(p, ep, optval) char *p, *ep; struct dhcp6_listval *optval; { struct dhcp6opt *opt; struct dhcp6opt_stcode stcodeopt; struct dhcp6opt_ia ia; struct dhcp6opt_ia_pd_prefix pd_prefix; struct dhcp6opt_ia_addr ia_addr; char *subp; struct dhcp6_listval *subov; int optlen, headlen, sublen, opttype; /* check invariant for safety */ if (p && ep <= p) return (-1); /* first, detect the length of the option head */ switch(optval->type) { case DHCP6_LISTVAL_IAPD: memset(&ia, 0, sizeof(ia)); headlen = sizeof(ia); opttype = DH6OPT_IA_PD; opt = (struct dhcp6opt *)(void *)&ia; break; case DHCP6_LISTVAL_IANA: memset(&ia, 0, sizeof(ia)); headlen = sizeof(ia); opttype = DH6OPT_IA_NA; opt = (struct dhcp6opt *)(void *)&ia; break; case DHCP6_LISTVAL_ADDR6: memset(&pd_prefix, 0, sizeof(pd_prefix)); headlen = sizeof(pd_prefix); opttype = DH6OPT_IA_PD_PREFIX; opt = (struct dhcp6opt *)&pd_prefix; break; case DHCP6_LISTVAL_PREFIX6: memset(&pd_prefix, 0, sizeof(pd_prefix)); headlen = sizeof(pd_prefix); opttype = DH6OPT_IA_PD_PREFIX; opt = (struct dhcp6opt *)&pd_prefix; break; case DHCP6_LISTVAL_STATEFULADDR6: memset(&ia_addr, 0, sizeof(ia_addr)); headlen = sizeof(ia_addr); opttype = DH6OPT_IAADDR; opt = (struct dhcp6opt *)&ia_addr; break; case DHCP6_LISTVAL_STCODE: memset(&stcodeopt, 0, sizeof(stcodeopt)); headlen = sizeof(stcodeopt); opttype = DH6OPT_STATUS_CODE; opt = (struct dhcp6opt *)(void *)&stcodeopt; break; default: /* * we encounter an unknown option. this should be an internal * error. */ d_printf(LOG_ERR, FNAME, "unknown option: code %d", optval->type); return (-1); } /* then, calculate the length of and/or fill in the sub-options */ subp = NULL; sublen = 0; if (p) subp = p + headlen; for (subov = TAILQ_FIRST(&optval->sublist); subov; subov = TAILQ_NEXT(subov, link)) { int s; if ((s = copyout_option(subp, ep, subov)) < 0) return (-1); if (p) subp += s; sublen += s; } /* finally, deal with the head part again */ optlen = headlen + sublen; if (!p) return(optlen); d_printf(LOG_DEBUG, FNAME, "set %s", dhcp6optstr(opttype)); if (ep - p < headlen) /* check it just in case */ return (-1); /* fill in the common part */ opt->dh6opt_type = htons(opttype); opt->dh6opt_len = htons(optlen - sizeof(struct dhcp6opt)); /* fill in type specific fields */ switch(optval->type) { case DHCP6_LISTVAL_IAPD: ia.dh6_ia_iaid = htonl(optval->val_ia.iaid); ia.dh6_ia_t1 = htonl(optval->val_ia.t1); ia.dh6_ia_t2 = htonl(optval->val_ia.t2); break; case DHCP6_LISTVAL_IANA: ia.dh6_ia_iaid = htonl(optval->val_ia.iaid); ia.dh6_ia_t1 = htonl(optval->val_ia.t1); ia.dh6_ia_t2 = htonl(optval->val_ia.t2); break; case DHCP6_LISTVAL_PREFIX6: pd_prefix.dh6_iapd_prefix_preferred_time = htonl(optval->val_prefix6.pltime); pd_prefix.dh6_iapd_prefix_valid_time = htonl(optval->val_prefix6.vltime); pd_prefix.dh6_iapd_prefix_prefix_len = optval->val_prefix6.plen; /* XXX: prefix_addr is badly aligned, so we need memcpy */ memcpy(&pd_prefix.dh6_iapd_prefix_prefix_addr, &optval->val_prefix6.addr, sizeof(struct in6_addr)); break; case DHCP6_LISTVAL_STATEFULADDR6: ia_addr.dh6_ia_addr_preferred_time = htonl(optval->val_statefuladdr6.pltime); ia_addr.dh6_ia_addr_valid_time = htonl(optval->val_statefuladdr6.vltime); ia_addr.dh6_ia_addr_addr = optval->val_statefuladdr6.addr; break; case DHCP6_LISTVAL_STCODE: stcodeopt.dh6_stcode_code = htons(optval->val_num16); break; default: /* * XXX: this case should be rejected at the beginning of this * function. */ return (-1); } /* copyout the data (p must be non NULL at this point) */ memcpy(p, opt, headlen); return (optlen); } void dhcp6_set_timeoparam(ev) struct dhcp6_event *ev; { ev->retrans = 0; ev->init_retrans = 0; ev->max_retrans_cnt = 0; ev->max_retrans_dur = 0; ev->max_retrans_time = 0; switch(ev->state) { case DHCP6S_SOLICIT: ev->init_retrans = SOL_TIMEOUT; ev->max_retrans_time = SOL_MAX_RT; break; case DHCP6S_INFOREQ: ev->init_retrans = INF_TIMEOUT; ev->max_retrans_time = INF_MAX_RT; break; case DHCP6S_REQUEST: ev->init_retrans = REQ_TIMEOUT; ev->max_retrans_time = REQ_MAX_RT; ev->max_retrans_cnt = REQ_MAX_RC; break; case DHCP6S_RENEW: ev->init_retrans = REN_TIMEOUT; ev->max_retrans_time = REN_MAX_RT; break; case DHCP6S_REBIND: ev->init_retrans = REB_TIMEOUT; ev->max_retrans_time = REB_MAX_RT; break; case DHCP6S_RELEASE: ev->init_retrans = REL_TIMEOUT; ev->max_retrans_cnt = REL_MAX_RC; break; default: d_printf(LOG_ERR, FNAME, "unexpected event state %d on %s", ev->state, ev->ifp->ifname); exit(1); } } void dhcp6_reset_timer(ev) struct dhcp6_event *ev; { double n, r; const char *statestr; struct timeval interval; switch(ev->state) { case DHCP6S_INIT: /* * The first Solicit message from the client on the interface * MUST be delayed by a random amount of time between * 0 and SOL_MAX_DELAY. * [RFC3315 17.1.2] * XXX: a random delay is also necessary before the first * information-request message. Fortunately, the parameters * and the algorithm for these two cases are the same. * [RFC3315 18.1.5] */ ev->retrans = (random() % (SOL_MAX_DELAY)); break; default: if (ev->state == DHCP6S_SOLICIT && ev->timeouts == 0) { /* * The first RT MUST be selected to be strictly * greater than IRT by choosing RAND to be strictly * greater than 0. * [RFC3315 17.1.2] */ r = (double)((random() % 1000) + 1) / 10000; n = ev->init_retrans + r * ev->init_retrans; } else { r = (double)((random() % 2000) - 1000) / 10000; if (ev->timeouts == 0) { n = ev->init_retrans + r * ev->init_retrans; } else n = 2 * ev->retrans + r * ev->retrans; } if (ev->max_retrans_time && n > ev->max_retrans_time) n = ev->max_retrans_time + r * ev->max_retrans_time; ev->retrans = (long)n; break; } interval.tv_sec = (ev->retrans * 1000) / 1000000; interval.tv_usec = (ev->retrans * 1000) % 1000000; dhcp6_set_timer(&interval, ev->timer); statestr = dhcp6_event_statestr(ev); d_printf(LOG_DEBUG, FNAME, "reset a timer on %s, " "state=%s, timeo=%d, retrans=%d", ev->ifp->ifname, statestr, ev->timeouts, ev->retrans); } int duidcpy(dd, ds) struct duid *dd, *ds; { dd->duid_len = ds->duid_len; if ((dd->duid_id = malloc(dd->duid_len)) == NULL) { d_printf(LOG_ERR, FNAME, "memory allocation failed"); return (-1); } memcpy(dd->duid_id, ds->duid_id, dd->duid_len); return (0); } int duidcmp(d1, d2) struct duid *d1, *d2; { if (d1->duid_len == d2->duid_len) { return (memcmp(d1->duid_id, d2->duid_id, d1->duid_len)); } else return (-1); } void duidfree(duid) struct duid *duid; { if (duid->duid_id) free(duid->duid_id); duid->duid_id = NULL; duid->duid_len = 0; } /* * Provide an NTP-format timestamp as a replay detection counter * as mentioned in RFC3315. */ #define JAN_1970 2208988800UL /* 1970 - 1900 in seconds */ int get_rdvalue(rdm, rdvalue, rdsize) int rdm; void *rdvalue; size_t rdsize; { #if defined(HAVE_CLOCK_GETTIME) struct timespec tp; double nsec; #else struct timeval tv; #endif uint32_t u32, l32; if (rdm != DHCP6_AUTHRDM_MONOCOUNTER) { d_printf(LOG_INFO, FNAME, "unsupported RDM (%d)", rdm); return (-1); } if (rdsize != sizeof(uint64_t)) { d_printf(LOG_INFO, FNAME, "unsupported RD size (%d)", rdsize); return (-1); } #if defined(HAVE_CLOCK_GETTIME) if (clock_gettime(CLOCK_REALTIME, &tp)) { d_printf(LOG_WARNING, FNAME, "clock_gettime failed: %s", strerror(errno)); return (-1); } u32 = (uint32_t)tp.tv_sec; u32 += JAN_1970; nsec = (double)tp.tv_nsec / 1e9 * 0x100000000ULL; /* nsec should be smaller than 2^32 */ l32 = (uint32_t)nsec; #else if (gettimeofday(&tv, NULL) != 0) { d_printf(LOG_WARNING, FNAME, "gettimeofday failed: %s", strerror(errno)); return (-1); } u32 = (uint32_t)tv.tv_sec; u32 += JAN_1970; l32 = (uint32_t)tv.tv_usec; #endif /* HAVE_CLOCK_GETTIME */ u32 = htonl(u32); l32 = htonl(l32); memcpy(rdvalue, &u32, sizeof(u32)); memcpy((char *)rdvalue + sizeof(u32), &l32, sizeof(l32)); return (0); } const char * dhcp6optstr(type) int type; { static char genstr[sizeof("opt_65535") + 1]; /* XXX thread unsafe */ if (type > 65535) return ("INVALID option"); switch(type) { case DH6OPT_CLIENTID: return ("client ID"); case DH6OPT_SERVERID: return ("server ID"); case DH6OPT_IA_NA: return ("identity association"); case DH6OPT_IA_TA: return ("IA for temporary"); case DH6OPT_IAADDR: return ("IA address"); case DH6OPT_ORO: return ("option request"); case DH6OPT_PREFERENCE: return ("preference"); case DH6OPT_ELAPSED_TIME: return ("elapsed time"); case DH6OPT_RELAY_MSG: return ("relay message"); case DH6OPT_AUTH: return ("authentication"); case DH6OPT_UNICAST: return ("server unicast"); case DH6OPT_STATUS_CODE: return ("status code"); case DH6OPT_RAPID_COMMIT: return ("rapid commit"); case DH6OPT_USER_CLASS: return ("user class"); case DH6OPT_VENDOR_CLASS: return ("vendor class"); case DH6OPT_VENDOR_OPTS: return ("vendor specific info"); case DH6OPT_INTERFACE_ID: return ("interface ID"); case DH6OPT_RECONF_MSG: return ("reconfigure message"); case DH6OPT_SIP_SERVER_D: return ("SIP domain name"); case DH6OPT_SIP_SERVER_A: return ("SIP server address"); case DH6OPT_DNS: return ("DNS"); case DH6OPT_DNSNAME: return ("domain search list"); case DH6OPT_NTP: return ("NTP server"); case DH6OPT_IA_PD: return ("IA_PD"); case DH6OPT_IA_PD_PREFIX: return ("IA_PD prefix"); case DH6OPT_REFRESHTIME: return ("information refresh time"); case DH6OPT_NIS_SERVERS: return ("NIS servers"); case DH6OPT_NISP_SERVERS: return ("NIS+ servers"); case DH6OPT_NIS_DOMAIN_NAME: return ("NIS domain name"); case DH6OPT_NISP_DOMAIN_NAME: return ("NIS+ domain name"); case DH6OPT_BCMCS_SERVER_D: return ("BCMCS domain name"); case DH6OPT_BCMCS_SERVER_A: return ("BCMCS server address"); case DH6OPT_GEOCONF_CIVIC: return ("Geoconf Civic"); case DH6OPT_REMOTE_ID: return ("remote ID"); case DH6OPT_SUBSCRIBER_ID: return ("subscriber ID"); case DH6OPT_CLIENT_FQDN: return ("client FQDN"); /* Either a known or an unknown option. RAW is a syntax, not an option case DHCPOPT_RAW: return ("raw"); */ default: snprintf(genstr, sizeof(genstr), "opt_%d", type); return (genstr); } } const char * dhcp6msgstr(type) int type; { static char genstr[sizeof("msg255") + 1]; /* XXX thread unsafe */ if (type > 255) return ("INVALID msg"); switch(type) { case DH6_SOLICIT: return ("solicit"); case DH6_ADVERTISE: return ("advertise"); case DH6_REQUEST: return ("request"); case DH6_CONFIRM: return ("confirm"); case DH6_RENEW: return ("renew"); case DH6_REBIND: return ("rebind"); case DH6_REPLY: return ("reply"); case DH6_RELEASE: return ("release"); case DH6_DECLINE: return ("decline"); case DH6_RECONFIGURE: return ("reconfigure"); case DH6_INFORM_REQ: return ("information request"); case DH6_RELAY_FORW: return ("relay-forward"); case DH6_RELAY_REPLY: return ("relay-reply"); default: snprintf(genstr, sizeof(genstr), "msg%d", type); return (genstr); } } const char * dhcp6_stcodestr(uint16_t code) { static char genstr[sizeof("code255") + 1]; /* XXX thread unsafe */ if (code > 255) return ("INVALID code"); switch(code) { case DH6OPT_STCODE_SUCCESS: return ("success"); case DH6OPT_STCODE_UNSPECFAIL: return ("unspec failure"); case DH6OPT_STCODE_NOADDRSAVAIL: return ("no addresses"); case DH6OPT_STCODE_NOBINDING: return ("no binding"); case DH6OPT_STCODE_NOTONLINK: return ("not on-link"); case DH6OPT_STCODE_USEMULTICAST: return ("use multicast"); case DH6OPT_STCODE_NOPREFIXAVAIL: return ("no prefixes"); default: snprintf(genstr, sizeof(genstr), "code%d", code); return (genstr); } } char * duidstr(duid) struct duid *duid; { size_t i; int n; char *cp, *ep; static char duidstr[sizeof("xx:") * 128 + sizeof("...")]; cp = duidstr; ep = duidstr + sizeof(duidstr); for (i = 0; i < duid->duid_len && i <= 128; i++) { n = snprintf(cp, ep - cp, "%s%02x", i == 0 ? "" : ":", duid->duid_id[i] & 0xff); if (n < 0) return NULL; cp += n; } if (i < duid->duid_len) snprintf(cp, ep - cp, "%s", "..."); return (duidstr); } const char *dhcp6_event_statestr(ev) struct dhcp6_event *ev; { switch(ev->state) { case DHCP6S_INIT: return ("INIT"); case DHCP6S_SOLICIT: return ("SOLICIT"); case DHCP6S_INFOREQ: return ("INFOREQ"); case DHCP6S_REQUEST: return ("REQUEST"); case DHCP6S_RENEW: return ("RENEW"); case DHCP6S_REBIND: return ("REBIND"); case DHCP6S_RELEASE: return ("RELEASE"); case DHCP6S_IDLE: return ("IDLE"); case DHCP6S_EXIT: return ("EXIT"); default: return ("???"); /* XXX */ } } void setloglevel(debuglevel) int debuglevel; { if (foreground) { switch(debuglevel) { case 0: debug_thresh = LOG_ERR; break; case 1: debug_thresh = LOG_INFO; break; default: debug_thresh = LOG_DEBUG; break; } } else { switch(debuglevel) { case 0: setlogmask(LOG_UPTO(LOG_ERR)); debug_thresh = LOG_ERR; break; case 1: setlogmask(LOG_UPTO(LOG_INFO)); debug_thresh = LOG_INFO; break; case 2: setlogmask(LOG_UPTO(LOG_DEBUG)); debug_thresh = LOG_DEBUG; break; } } } void d_printf(int level, const char *fname, const char *fmt, ...) { va_list ap; char logbuf[LINE_MAX]; int printfname = 1; va_start(ap, fmt); vsnprintf(logbuf, sizeof(logbuf), fmt, ap); if (*fname == '\0') printfname = 0; if (foreground && debug_thresh >= level) { time_t now; struct tm *tm_now; const char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; if ((now = time(NULL)) < 0) exit(1); /* XXX */ tm_now = localtime(&now); fprintf(stderr, "%3s/%02d/%04d %02d:%02d:%02d: %s%s%s\n", month[tm_now->tm_mon], tm_now->tm_mday, tm_now->tm_year + 1900, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec, fname, printfname ? ": " : "", logbuf); } else { /* * XXX DEBUG/INFO require NOTICE in order to * to appear in the OPNsense system log file. */ if (debug_thresh <= level && level > LOG_NOTICE) { level = LOG_NOTICE; } syslog(level, "%s%s%s", fname, printfname ? ": " : "", logbuf); } } int ifaddrconf(cmd, ifname, addr, plen, pltime, vltime) ifaddrconf_cmd_t cmd; char *ifname; struct sockaddr_in6 *addr; int plen; int pltime; int vltime; { #ifdef __KAME__ struct in6_aliasreq req; #endif #ifdef __linux__ struct in6_ifreq req; struct ifreq ifr; #endif #ifdef __sun__ struct lifreq req; #endif unsigned long ioctl_cmd; const char *cmdstr; int s; /* XXX overhead */ switch(cmd) { case IFADDRCONF_ADD: cmdstr = "add"; #ifdef __KAME__ ioctl_cmd = SIOCAIFADDR_IN6; #endif #ifdef __linux__ ioctl_cmd = SIOCSIFADDR; #endif #ifdef __sun__ ioctl_cmd = SIOCLIFADDIF; #endif break; case IFADDRCONF_REMOVE: cmdstr = "remove"; #ifdef __KAME__ ioctl_cmd = SIOCDIFADDR_IN6; #endif #ifdef __linux__ ioctl_cmd = SIOCDIFADDR; #endif #ifdef __sun__ ioctl_cmd = SIOCLIFREMOVEIF; #endif break; default: return (-1); } if ((s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) { d_printf(LOG_ERR, FNAME, "can't open a temporary socket: %s", strerror(errno)); return (-1); } memset(&req, 0, sizeof(req)); #ifdef __KAME__ req.ifra_addr = *addr; memcpy(req.ifra_name, ifname, sizeof(req.ifra_name)); (void)sa6_plen2mask(&req.ifra_prefixmask, plen); /* XXX: should lifetimes be calculated based on the lease duration? */ req.ifra_lifetime.ia6t_vltime = vltime; req.ifra_lifetime.ia6t_pltime = pltime; #endif #ifdef __linux__ memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); if (ioctl(s, SIOGIFINDEX, &ifr) < 0) { d_printf(LOG_NOTICE, FNAME, "failed to get the index of %s: %s", ifname, strerror(errno)); close(s); return (-1); } memcpy(&req.ifr6_addr, &addr->sin6_addr, sizeof(struct in6_addr)); req.ifr6_prefixlen = plen; req.ifr6_ifindex = ifr.ifr_ifindex; #endif #ifdef __sun__ strncpy(req.lifr_name, ifname, sizeof (req.lifr_name)); #endif if (ioctl(s, ioctl_cmd, &req)) { d_printf(LOG_NOTICE, FNAME, "failed to %s an address on %s: %s", cmdstr, ifname, strerror(errno)); close(s); return (-1); } #ifdef __sun__ memcpy(&req.lifr_addr, addr, sizeof (*addr)); if (ioctl(s, SIOCSLIFADDR, &req) == -1) { d_printf(LOG_NOTICE, FNAME, "failed to %s new address on %s: %s", cmdstr, ifname, strerror(errno)); close(s); return (-1); } #endif d_printf(LOG_INFO, FNAME, "%s an address %s/%d on %s", cmdstr, addr2str((struct sockaddr *)addr), plen, ifname); close(s); return (0); } int safefile(path) const char *path; { struct stat s; uid_t myuid; /* no setuid */ if (getuid() != geteuid()) { d_printf(LOG_NOTICE, FNAME, "setuid'ed execution not allowed"); return (-1); } if (lstat(path, &s) != 0) { d_printf(LOG_NOTICE, FNAME, "lstat failed: %s", strerror(errno)); return (-1); } /* the file must be owned by the running uid */ myuid = getuid(); if (s.st_uid != myuid) { d_printf(LOG_NOTICE, FNAME, "%s has invalid owner uid", path); return (-1); } switch (s.st_mode & S_IFMT) { case S_IFREG: break; default: d_printf(LOG_NOTICE, FNAME, "%s is an invalid file type 0x%o", path, (s.st_mode & S_IFMT)); return (-1); } return (0); } int get_val32(char **bpp, int *lenp, uint32_t *valp) { char *bp = *bpp; size_t len = (size_t)*lenp; uint32_t i32; if (len < sizeof(*valp)) return (-1); memcpy(&i32, bp, sizeof(i32)); *valp = ntohl(i32); *bpp = bp + sizeof(*valp); *lenp = len - sizeof(*valp); return (0); } int get_val(char **bpp, int *lenp, void *valp, size_t vallen) { char *bp = *bpp; size_t len = (size_t)*lenp; if (len < vallen) return (-1); memcpy(valp, bp, vallen); *bpp = bp + vallen; *lenp = len - vallen; return (0); }