static int parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, int opt, const char *arg, struct dhcp_opt **ldop, struct dhcp_opt **edop) { int i, l, t; unsigned int u; char *p = NULL, *fp, *np, **nconf; ssize_t s; struct in_addr addr, addr2; in_addr_t *naddr; struct rt *rt; const struct dhcp_opt *d; uint8_t *request, *require, *no; struct dhcp_opt **dop, *ndop; size_t *dop_len, dl; struct vivco *vivco; struct token *token; #ifdef INET6 size_t sl; struct if_ia *ia; uint8_t iaid[4]; struct if_sla *sla, *slap; #endif dop = NULL; dop_len = NULL; #ifdef INET6 i = 0; #endif switch(opt) { case 'f': /* FALLTHROUGH */ case 'g': /* FALLTHROUGH */ case 'n': /* FALLTHROUGH */ case 'x': /* FALLTHROUGH */ case 'T': /* FALLTHROUGH */ case 'U': /* FALLTHROUGH */ case 'V': /* We need to handle non interface options */ break; case 'b': ifo->options |= DHCPCD_BACKGROUND; break; case 'c': free(ifo->script); ifo->script = strdup(arg); if (ifo->script == NULL) syslog(LOG_ERR, "%s: %m", __func__); break; case 'd': ifo->options |= DHCPCD_DEBUG; break; case 'e': add_environ(ifo, arg, 1); break; case 'h': if (!arg) { ifo->options |= DHCPCD_HOSTNAME; break; } s = parse_string(ifo->hostname, HOSTNAME_MAX_LEN, arg); if (s == -1) { syslog(LOG_ERR, "hostname: %m"); return -1; } if (s != 0 && ifo->hostname[0] == '.') { syslog(LOG_ERR, "hostname cannot begin with ."); return -1; } ifo->hostname[s] = '\0'; if (ifo->hostname[0] == '\0') ifo->options &= ~DHCPCD_HOSTNAME; else ifo->options |= DHCPCD_HOSTNAME; break; case 'i': if (arg) s = parse_string((char *)ifo->vendorclassid + 1, VENDORCLASSID_MAX_LEN, arg); else s = 0; if (s == -1) { syslog(LOG_ERR, "vendorclassid: %m"); return -1; } *ifo->vendorclassid = (uint8_t)s; break; case 'k': ifo->options |= DHCPCD_RELEASE; break; case 'l': if (*arg == '-') { syslog(LOG_ERR, "leasetime must be a positive value"); return -1; } errno = 0; ifo->leasetime = (uint32_t)strtoul(arg, NULL, 0); if (errno == EINVAL || errno == ERANGE) { syslog(LOG_ERR, "`%s' out of range", arg); return -1; } break; case 'm': ifo->metric = atoint(arg); if (ifo->metric < 0) { syslog(LOG_ERR, "metric must be a positive value"); return -1; } break; case 'o': arg = set_option_space(ctx, arg, &d, &dl, ifo, &request, &require, &no); if (make_option_mask(d, dl, request, arg, 1) != 0) { syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case 'p': ifo->options |= DHCPCD_PERSISTENT; break; case 'q': ifo->options |= DHCPCD_QUIET; break; case 'r': if (parse_addr(&ifo->req_addr, NULL, arg) != 0) return -1; ifo->options |= DHCPCD_REQUEST; ifo->req_mask.s_addr = 0; break; case 's': if (ifo->options & DHCPCD_IPV6 && !(ifo->options & DHCPCD_IPV4)) { ifo->options |= DHCPCD_INFORM; break; } if (arg && *arg != '\0') { if (parse_addr(&ifo->req_addr, &ifo->req_mask, arg) != 0) return -1; } else { ifo->req_addr.s_addr = 0; ifo->req_mask.s_addr = 0; } ifo->options |= DHCPCD_INFORM | DHCPCD_PERSISTENT; ifo->options &= ~(DHCPCD_ARP | DHCPCD_STATIC); break; case 't': ifo->timeout = atoint(arg); if (ifo->timeout < 0) { syslog(LOG_ERR, "timeout must be a positive value"); return -1; } break; case 'u': s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1; s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2, s, arg); if (s == -1) { syslog(LOG_ERR, "userclass: %m"); return -1; } if (s != 0) { ifo->userclass[ifo->userclass[0] + 1] = s; ifo->userclass[0] += s + 1; } break; case 'v': p = strchr(arg, ','); if (!p || !p[1]) { syslog(LOG_ERR, "invalid vendor format: %s", arg); return -1; } /* If vendor starts with , then it is not encapsulated */ if (p == arg) { arg++; s = parse_string((char *)ifo->vendor + 1, VENDOR_MAX_LEN, arg); if (s == -1) { syslog(LOG_ERR, "vendor: %m"); return -1; } ifo->vendor[0] = (uint8_t)s; ifo->options |= DHCPCD_VENDORRAW; break; } /* Encapsulated vendor options */ if (ifo->options & DHCPCD_VENDORRAW) { ifo->options &= ~DHCPCD_VENDORRAW; ifo->vendor[0] = 0; } /* No need to strip the comma */ i = atoint(arg); if (i < 1 || i > 254) { syslog(LOG_ERR, "vendor option should be between" " 1 and 254 inclusive"); return -1; } arg = p + 1; s = VENDOR_MAX_LEN - ifo->vendor[0] - 2; if (inet_aton(arg, &addr) == 1) { if (s < 6) { s = -1; errno = ENOBUFS; } else { memcpy(ifo->vendor + ifo->vendor[0] + 3, &addr.s_addr, sizeof(addr.s_addr)); s = sizeof(addr.s_addr); } } else { s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3, s, arg); } if (s == -1) { syslog(LOG_ERR, "vendor: %m"); return -1; } if (s != 0) { ifo->vendor[ifo->vendor[0] + 1] = i; ifo->vendor[ifo->vendor[0] + 2] = s; ifo->vendor[0] += s + 2; } break; case 'w': ifo->options |= DHCPCD_WAITIP; if (arg != NULL && arg[0] != '\0') { if (arg[0] == '4' || arg[1] == '4') ifo->options |= DHCPCD_WAITIP4; if (arg[0] == '6' || arg[1] == '6') ifo->options |= DHCPCD_WAITIP6; } break; case 'y': ifo->reboot = atoint(arg); if (ifo->reboot < 0) { syslog(LOG_ERR, "reboot must be a positive value"); return -1; } break; case 'z': if (ifname == NULL) ctx->ifav = splitv(&ctx->ifac, ctx->ifav, arg); break; case 'A': ifo->options &= ~DHCPCD_ARP; /* IPv4LL requires ARP */ ifo->options &= ~DHCPCD_IPV4LL; break; case 'B': ifo->options &= ~DHCPCD_DAEMONISE; break; case 'C': /* Commas to spaces for shell */ while ((p = strchr(arg, ','))) *p = ' '; s = strlen("skip_hooks=") + strlen(arg) + 1; p = malloc(sizeof(char) * s); if (p == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } snprintf(p, s, "skip_hooks=%s", arg); add_environ(ifo, p, 0); free(p); break; case 'D': ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; break; case 'E': ifo->options |= DHCPCD_LASTLEASE; break; case 'F': if (!arg) { ifo->fqdn = FQDN_BOTH; break; } if (strcmp(arg, "none") == 0) ifo->fqdn = FQDN_NONE; else if (strcmp(arg, "ptr") == 0) ifo->fqdn = FQDN_PTR; else if (strcmp(arg, "both") == 0) ifo->fqdn = FQDN_BOTH; else if (strcmp(arg, "disable") == 0) ifo->fqdn = FQDN_DISABLE; else { syslog(LOG_ERR, "invalid value `%s' for FQDN", arg); return -1; } break; case 'G': ifo->options &= ~DHCPCD_GATEWAY; break; case 'H': ifo->options |= DHCPCD_XID_HWADDR; break; case 'I': /* Strings have a type of 0 */; ifo->clientid[1] = 0; if (arg) s = parse_string_hwaddr((char *)ifo->clientid + 1, CLIENTID_MAX_LEN, arg, 1); else s = 0; if (s == -1) { syslog(LOG_ERR, "clientid: %m"); return -1; } ifo->options |= DHCPCD_CLIENTID; ifo->clientid[0] = (uint8_t)s; break; case 'J': ifo->options |= DHCPCD_BROADCAST; break; case 'K': ifo->options &= ~DHCPCD_LINK; break; case 'L': ifo->options &= ~DHCPCD_IPV4LL; break; case 'M': ifo->options |= DHCPCD_MASTER; break; case 'O': arg = set_option_space(ctx, arg, &d, &dl, ifo, &request, &require, &no); if (make_option_mask(d, dl, request, arg, -1) != 0 || make_option_mask(d, dl, require, arg, -1) != 0 || make_option_mask(d, dl, no, arg, 1) != 0) { syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case 'Q': arg = set_option_space(ctx, arg, &d, &dl, ifo, &request, &require, &no); if (make_option_mask(d, dl, require, arg, 1) != 0 || make_option_mask(d, dl, request, arg, 1) != 0) { syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case 'S': p = strchr(arg, '='); if (p == NULL) { syslog(LOG_ERR, "static assignment required"); return -1; } p++; if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { if (parse_addr(&ifo->req_addr, ifo->req_mask.s_addr == 0 ? &ifo->req_mask : NULL, p) != 0) return -1; ifo->options |= DHCPCD_STATIC; ifo->options &= ~DHCPCD_INFORM; } else if (strncmp(arg, "subnet_mask=", strlen("subnet_mask=")) == 0) { if (parse_addr(&ifo->req_mask, NULL, p) != 0) return -1; } else if (strncmp(arg, "routes=", strlen("routes=")) == 0 || strncmp(arg, "static_routes=", strlen("static_routes=")) == 0 || strncmp(arg, "classless_static_routes=", strlen("classless_static_routes=")) == 0 || strncmp(arg, "ms_classless_static_routes=", strlen("ms_classless_static_routes=")) == 0) { fp = np = strwhite(p); if (np == NULL) { syslog(LOG_ERR, "all routes need a gateway"); return -1; } *np++ = '\0'; np = strskipwhite(np); if (ifo->routes == NULL) { ifo->routes = malloc(sizeof(*ifo->routes)); if (ifo->routes == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } TAILQ_INIT(ifo->routes); } rt = malloc(sizeof(*rt)); if (rt == NULL) { syslog(LOG_ERR, "%s: %m", __func__); *fp = ' '; return -1; } if (parse_addr(&rt->dest, &rt->net, p) == -1 || parse_addr(&rt->gate, NULL, np) == -1) { free(rt); *fp = ' '; return -1; } TAILQ_INSERT_TAIL(ifo->routes, rt, next); *fp = ' '; } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) { if (ifo->routes == NULL) { ifo->routes = malloc(sizeof(*ifo->routes)); if (ifo->routes == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } TAILQ_INIT(ifo->routes); } rt = malloc(sizeof(*rt)); if (rt == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } rt->dest.s_addr = INADDR_ANY; rt->net.s_addr = INADDR_ANY; if (parse_addr(&rt->gate, NULL, p) == -1) { free(rt); return -1; } TAILQ_INSERT_TAIL(ifo->routes, rt, next); } else { s = 0; if (ifo->config != NULL) { while (ifo->config[s] != NULL) { if (strncmp(ifo->config[s], arg, p - arg) == 0) { p = strdup(arg); if (p == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } free(ifo->config[s]); ifo->config[s] = p; return 1; } s++; } } p = strdup(arg); if (p == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } nconf = realloc(ifo->config, sizeof(char *) * (s + 2)); if (nconf == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ifo->config = nconf; ifo->config[s] = p; ifo->config[s + 1] = NULL; } break; case 'W': if (parse_addr(&addr, &addr2, arg) != 0) return -1; if (strchr(arg, '/') == NULL) addr2.s_addr = INADDR_BROADCAST; naddr = realloc(ifo->whitelist, sizeof(in_addr_t) * (ifo->whitelist_len + 2)); if (naddr == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ifo->whitelist = naddr; ifo->whitelist[ifo->whitelist_len++] = addr.s_addr; ifo->whitelist[ifo->whitelist_len++] = addr2.s_addr; break; case 'X': if (parse_addr(&addr, &addr2, arg) != 0) return -1; if (strchr(arg, '/') == NULL) addr2.s_addr = INADDR_BROADCAST; naddr = realloc(ifo->blacklist, sizeof(in_addr_t) * (ifo->blacklist_len + 2)); if (naddr == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ifo->blacklist = naddr; ifo->blacklist[ifo->blacklist_len++] = addr.s_addr; ifo->blacklist[ifo->blacklist_len++] = addr2.s_addr; break; case 'Z': if (ifname == NULL) ctx->ifdv = splitv(&ctx->ifdc, ctx->ifdv, arg); break; case '4': ifo->options &= ~DHCPCD_IPV6; ifo->options |= DHCPCD_IPV4; break; case '6': ifo->options &= ~DHCPCD_IPV4; ifo->options |= DHCPCD_IPV6; break; case O_NOIPV4: ifo->options &= ~DHCPCD_IPV4; break; case O_NOIPV6: ifo->options &= ~DHCPCD_IPV6; break; #ifdef INET case O_ARPING: while (arg && *arg != '\0') { fp = strwhite(arg); if (fp) *fp++ = '\0'; if (parse_addr(&addr, NULL, arg) != 0) return -1; naddr = realloc(ifo->arping, sizeof(in_addr_t) * (ifo->arping_len + 1)); if (naddr == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ifo->arping = naddr; ifo->arping[ifo->arping_len++] = addr.s_addr; arg = strskipwhite(fp); } break; case O_DESTINATION: if (make_option_mask(ctx->dhcp_opts, ctx->dhcp_opts_len, ifo->dstmask, arg, 2) != 0) { if (errno == EINVAL) syslog(LOG_ERR, "option `%s' does not take" " an IPv4 address", arg); else syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case O_FALLBACK: free(ifo->fallback); ifo->fallback = strdup(arg); if (ifo->fallback == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } break; #endif case O_IAID: if (ifname == NULL) { syslog(LOG_ERR, "IAID must belong in an interface block"); return -1; } if (parse_iaid(ifo->iaid, arg, sizeof(ifo->iaid)) == -1) return -1; ifo->options |= DHCPCD_IAID; break; case O_IPV6RS: ifo->options |= DHCPCD_IPV6RS; break; case O_NOIPV6RS: ifo->options &= ~DHCPCD_IPV6RS; break; case O_IPV6RA_FORK: ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; break; case O_IPV6RA_OWN: ifo->options |= DHCPCD_IPV6RA_OWN; break; case O_IPV6RA_OWN_D: ifo->options |= DHCPCD_IPV6RA_OWN_DEFAULT; break; case O_NOALIAS: ifo->options |= DHCPCD_NOALIAS; break; #ifdef INET6 case O_IA_NA: i = D6_OPTION_IA_NA; /* FALLTHROUGH */ case O_IA_TA: if (i == 0) i = D6_OPTION_IA_TA; /* FALLTHROUGH */ case O_IA_PD: if (i == 0) { if (ifname == NULL) { syslog(LOG_ERR, "IA PD must belong in an interface block"); return -1; } i = D6_OPTION_IA_PD; } if (arg != NULL && ifname == NULL) { syslog(LOG_ERR, "IA with IAID must belong in an interface block"); return -1; } ifo->options |= DHCPCD_IA_FORCED; if (ifo->ia_type != 0 && ifo->ia_type != i) { syslog(LOG_ERR, "cannot specify a different IA type"); return -1; } ifo->ia_type = i; if (arg == NULL) break; fp = strwhite(arg); if (fp) *fp++ = '\0'; if (parse_iaid(iaid, arg, sizeof(iaid)) == -1) return -1; ia = NULL; for (sl = 0; sl < ifo->ia_len; sl++) { if (ifo->ia[sl].iaid[0] == iaid[0] && ifo->ia[sl].iaid[1] == iaid[1] && ifo->ia[sl].iaid[2] == iaid[2] && ifo->ia[sl].iaid[3] == iaid[3]) { ia = &ifo->ia[sl]; break; } } if (ia == NULL) { ia = realloc(ifo->ia, sizeof(*ifo->ia) * (ifo->ia_len + 1)); if (ia == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ifo->ia = ia; ia = &ifo->ia[ifo->ia_len++]; ia->iaid[0] = iaid[0]; ia->iaid[1] = iaid[1]; ia->iaid[2] = iaid[2]; ia->iaid[3] = iaid[3]; ia->sla = NULL; ia->sla_len = 0; } if (ifo->ia_type != D6_OPTION_IA_PD) break; for (p = fp; p; p = fp) { fp = strwhite(p); if (fp) { *fp++ = '\0'; fp = strskipwhite(fp); } sla = realloc(ia->sla, sizeof(*ia->sla) * (ia->sla_len + 1)); if (sla == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ia->sla = sla; sla = &ia->sla[ia->sla_len++]; np = strchr(p, '/'); if (np) *np++ = '\0'; if (strcmp(ifname, p) == 0) { syslog(LOG_ERR, "%s: cannot assign IA_PD to itself", ifname); return -1; } if (strlcpy(sla->ifname, p, sizeof(sla->ifname)) >= sizeof(sla->ifname)) { syslog(LOG_ERR, "%s: interface name too long", arg); return -1; } p = np; if (p) { np = strchr(p, '/'); if (np) *np++ = '\0'; if (*p == '\0') sla->sla_set = 0; else { errno = 0; sla->sla = atoint(p); sla->sla_set = 1; if (errno) return -1; } if (np) { sla->prefix_len = atoint(np); if (sla->prefix_len < 0 || sla->prefix_len > 128) return -1; } else sla->prefix_len = 64; } else { sla->sla_set = 0; /* Sanity - check there are no more * unspecified SLA's */ for (sl = 0; sl < ia->sla_len - 1; sl++) { slap = &ia->sla[sl]; if (slap->sla_set == 0 && strcmp(slap->ifname, sla->ifname) == 0) { syslog(LOG_WARNING, "%s: cannot specify the " "same interface twice with " "an automatic SLA", sla->ifname); ia->sla_len--; break; } } } } #endif break; case O_HOSTNAME_SHORT: ifo->options |= DHCPCD_HOSTNAME | DHCPCD_HOSTNAME_SHORT; break; case O_DEV: #ifdef PLUGIN_DEV if (ctx->dev_load) free(ctx->dev_load); ctx->dev_load = strdup(arg); #endif break; case O_NODEV: ifo->options &= ~DHCPCD_DEV; break; case O_DEFINE: dop = &ifo->dhcp_override; dop_len = &ifo->dhcp_override_len; /* FALLTHROUGH */ case O_DEFINE6: if (dop == NULL) { dop = &ifo->dhcp6_override; dop_len = &ifo->dhcp6_override_len; } /* FALLTHROUGH */ case O_VENDOPT: if (dop == NULL) { dop = &ifo->vivso_override; dop_len = &ifo->vivso_override_len; } *edop = *ldop = NULL; /* FALLTHROUGH */ case O_EMBED: if (dop == NULL) { if (*edop) { dop = &(*edop)->embopts; dop_len = &(*edop)->embopts_len; } else if (ldop) { dop = &(*ldop)->embopts; dop_len = &(*ldop)->embopts_len; } else { syslog(LOG_ERR, "embed must be after a define or encap"); return -1; } } /* FALLTHROUGH */ case O_ENCAP: if (dop == NULL) { if (*ldop == NULL) { syslog(LOG_ERR, "encap must be after a define"); return -1; } dop = &(*ldop)->encopts; dop_len = &(*ldop)->encopts_len; } /* Shared code for define, define6, embed and encap */ /* code */ if (opt == O_EMBED) /* Embedded options don't have codes */ u = 0; else { fp = strwhite(arg); if (fp == NULL) { syslog(LOG_ERR, "invalid syntax: %s", arg); return -1; } *fp++ = '\0'; errno = 0; u = strtoul(arg, &np, 0); if (u > UINT32_MAX || errno != 0 || *np != '\0') { syslog(LOG_ERR, "invalid code: %s", arg); return -1; } arg = strskipwhite(fp); if (arg == NULL) { syslog(LOG_ERR, "invalid syntax"); return -1; } } /* type */ fp = strwhite(arg); if (fp) *fp++ = '\0'; np = strchr(arg, ':'); /* length */ if (np) { *np++ = '\0'; if ((l = atoint(np)) == -1) return -1; } else l = 0; t = 0; if (strcasecmp(arg, "request") == 0) { t |= REQUEST; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { syslog(LOG_ERR, "incomplete request type"); return -1; } *fp++ = '\0'; } else if (strcasecmp(arg, "norequest") == 0) { t |= NOREQ; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { syslog(LOG_ERR, "incomplete request type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "index") == 0) { t |= INDEX; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { syslog(LOG_ERR, "incomplete index type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "array") == 0) { t |= ARRAY; arg = strskipwhite(fp); fp = strwhite(arg); if (fp == NULL) { syslog(LOG_ERR, "incomplete array type"); return -1; } *fp++ = '\0'; } if (strcasecmp(arg, "ipaddress") == 0) t |= ADDRIPV4; else if (strcasecmp(arg, "ip6address") == 0) t |= ADDRIPV6; else if (strcasecmp(arg, "string") == 0) t |= STRING; else if (strcasecmp(arg, "byte") == 0) t |= UINT8; else if (strcasecmp(arg, "uint16") == 0) t |= UINT16; else if (strcasecmp(arg, "int16") == 0) t |= SINT16; else if (strcasecmp(arg, "uint32") == 0) t |= UINT32; else if (strcasecmp(arg, "int32") == 0) t |= SINT32; else if (strcasecmp(arg, "flag") == 0) t |= FLAG; else if (strcasecmp(arg, "domain") == 0) t |= STRING | RFC3397; else if (strcasecmp(arg, "binhex") == 0) t |= BINHEX; else if (strcasecmp(arg, "embed") == 0) t |= EMBED; else if (strcasecmp(arg, "encap") == 0) t |= ENCAP; else if (strcasecmp(arg, "rfc3361") ==0) t |= STRING | RFC3361; else if (strcasecmp(arg, "rfc3442") ==0) t |= STRING | RFC3442; else if (strcasecmp(arg, "rfc5969") == 0) t |= STRING | RFC5969; else if (strcasecmp(arg, "option") == 0) t |= OPTION; else { syslog(LOG_ERR, "unknown type: %s", arg); return -1; } if (l && !(t & (STRING | BINHEX))) { syslog(LOG_WARNING, "ignoring length for type `%s'", arg); l = 0; } if (t & ARRAY && t & (STRING | BINHEX)) { syslog(LOG_WARNING, "ignoring array for strings"); t &= ~ARRAY; } /* variable */ if (!fp) { if (!(t & OPTION)) { syslog(LOG_ERR, "type %s requires a variable name", arg); return -1; } np = NULL; } else { arg = strskipwhite(fp); fp = strwhite(arg); if (fp) *fp++ = '\0'; np = strdup(arg); if (np == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } } if (opt != O_EMBED) { for (dl = 0, ndop = *dop; dl < *dop_len; dl++, ndop++) { /* type 0 seems freshly malloced struct * for us to use */ if (ndop->option == u || ndop->type == 0) break; } if (dl == *dop_len) ndop = NULL; } else ndop = NULL; if (ndop == NULL) { if ((ndop = realloc(*dop, sizeof(**dop) * ((*dop_len) + 1))) == NULL) { syslog(LOG_ERR, "%s: %m", __func__); free(np); return -1; } *dop = ndop; ndop = &(*dop)[(*dop_len)++]; ndop->embopts = NULL; ndop->embopts_len = 0; ndop->encopts = NULL; ndop->encopts_len = 0; } else free_dhcp_opt_embenc(ndop); ndop->option = u; /* could have been 0 */ ndop->type = t; ndop->len = l; ndop->var = np; /* Save the define for embed and encap options */ if (opt == O_DEFINE || opt == O_DEFINE6 || opt == O_VENDOPT) *ldop = ndop; else if (opt == O_ENCAP) *edop = ndop; break; case O_VENDCLASS: fp = strwhite(arg); if (fp) *fp++ = '\0'; errno = 0; u = strtoul(arg, &np, 0); if (u > UINT32_MAX || errno != 0 || *np != '\0') { syslog(LOG_ERR, "invalid code: %s", arg); return -1; } if (fp) { s = parse_string(NULL, 0, fp); if (s == -1) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } if (s + (sizeof(uint16_t) * 2) > UINT16_MAX) { syslog(LOG_ERR, "vendor class is too big"); return -1; } np = malloc(s); if (np == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } parse_string(np, s, fp); } else { s = 0; np = NULL; } vivco = realloc(ifo->vivco, sizeof(*ifo->vivco) * (ifo->vivco_len + 1)); if (vivco == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } ifo->vivco = vivco; ifo->vivco_en = u; vivco = &ifo->vivco[ifo->vivco_len++]; vivco->len = s; vivco->data = (uint8_t *)np; break; case O_AUTHPROTOCOL: fp = strwhite(arg); if (fp) *fp++ = '\0'; if (strcasecmp(arg, "token") == 0) ifo->auth.protocol = AUTH_PROTO_TOKEN; else if (strcasecmp(arg, "delayed") == 0) ifo->auth.protocol = AUTH_PROTO_DELAYED; else if (strcasecmp(arg, "delayedrealm") == 0) ifo->auth.protocol = AUTH_PROTO_DELAYEDREALM; else { syslog(LOG_ERR, "%s: unsupported protocol", arg); return -1; } arg = strskipwhite(fp); fp = strwhite(arg); if (arg == NULL) { ifo->auth.options |= DHCPCD_AUTH_SEND; ifo->auth.algorithm = AUTH_ALG_HMAC_MD5; ifo->auth.rdm = AUTH_RDM_MONOTONIC; break; } if (fp) *fp++ = '\0'; if (strcasecmp(arg, "hmacmd5") == 0 || strcasecmp(arg, "hmac-md5") == 0) ifo->auth.algorithm = AUTH_ALG_HMAC_MD5; else { syslog(LOG_ERR, "%s: unsupported algorithm", arg); return 1; } arg = fp; if (arg == NULL) { ifo->auth.options |= DHCPCD_AUTH_SEND; ifo->auth.rdm = AUTH_RDM_MONOTONIC; break; } if (strcasecmp(arg, "monocounter") == 0) { ifo->auth.rdm = AUTH_RDM_MONOTONIC; ifo->auth.options |= DHCPCD_AUTH_RDM_COUNTER; } else if (strcasecmp(arg, "monotonic") ==0 || strcasecmp(arg, "monotime") == 0) ifo->auth.rdm = AUTH_RDM_MONOTONIC; else { syslog(LOG_ERR, "%s: unsupported RDM", arg); return -1; } ifo->auth.options |= DHCPCD_AUTH_SEND; break; case O_AUTHTOKEN: fp = strwhite(arg); if (fp == NULL) { syslog(LOG_ERR, "authtoken requires a realm"); return -1; } *fp++ = '\0'; token = malloc(sizeof(*token)); if (token == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } if (parse_uint32(&token->secretid, arg) == -1) { syslog(LOG_ERR, "%s: not a number", arg); free(token); return -1; } arg = fp; fp = strend(arg); if (fp == NULL) { syslog(LOG_ERR, "authtoken requies an a key"); free(token); return -1; } *fp++ = '\0'; token->realm_len = parse_string(NULL, 0, arg); if (token->realm_len) { token->realm = malloc(token->realm_len); if (token->realm == NULL) { free(token); syslog(LOG_ERR, "%s: %m", __func__); return -1; } parse_string((char *)token->realm, token->realm_len, arg); } arg = fp; fp = strend(arg); if (fp == NULL) { syslog(LOG_ERR, "authtoken requies an an expiry date"); free(token->realm); free(token); return -1; } *fp++ = '\0'; if (*arg == '"') { arg++; np = strchr(arg, '"'); if (np) *np = '\0'; } if (strcmp(arg, "0") == 0 || strcasecmp(arg, "forever") == 0) token->expire =0; else { struct tm tm; memset(&tm, 0, sizeof(tm)); if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) { syslog(LOG_ERR, "%s: invalid date time", arg); free(token->realm); free(token); return -1; } if ((token->expire = mktime(&tm)) == (time_t)-1) { syslog(LOG_ERR, "%s: mktime: %m", __func__); free(token->realm); free(token); return -1; } } arg = fp; token->key_len = parse_string(NULL, 0, arg); if (token->key_len == 0) { syslog(LOG_ERR, "authtoken needs a key"); free(token->realm); free(token); return -1; } token->key = malloc(token->key_len); parse_string((char *)token->key, token->key_len, arg); TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next); break; case O_AUTHNOTREQUIRED: ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE; break; case O_NODHCP: ifo->options &= ~DHCPCD_DHCP; break; case O_NODHCP6: ifo->options &= ~DHCPCD_DHCP6; break; default: return 0; } return 1; }
static int parse_option(struct if_options *ifo, int opt, const char *arg) { int i; char *p = NULL, *np; ssize_t s; struct in_addr addr, addr2; struct rt *rt; switch(opt) { case 'a': /* FALLTHROUGH */ case 'f': /* FALLTHROUGH */ case 'g': /* FALLTHROUGH */ case 'n': /* FALLTHROUGH */ case 'x': /* FALLTHROUGH */ case 'T': /* FALLTHROUGH */ case 'U': /* We need to handle non interface options */ break; case 'b': ifo->options |= DHCPCD_BACKGROUND; break; case 'c': strlcpy(ifo->script, arg, sizeof(ifo->script)); break; case 'd': ifo->options |= DHCPCD_DEBUG; break; case 'e': add_environ(ifo, arg, 1); break; case 'h': if (arg) { s = parse_string(ifo->hostname, HOSTNAME_MAX_LEN, arg); if (s == -1) { syslog(LOG_ERR, "hostname: %m"); return -1; } if (s != 0 && ifo->hostname[0] == '.') { syslog(LOG_ERR, "hostname cannot begin with ."); return -1; } ifo->hostname[s] = '\0'; } if (ifo->hostname[0] == '\0') ifo->options &= ~DHCPCD_HOSTNAME; else ifo->options |= DHCPCD_HOSTNAME; break; case 'i': if (arg) s = parse_string((char *)ifo->vendorclassid + 1, VENDORCLASSID_MAX_LEN, arg); else s = 0; if (s == -1) { syslog(LOG_ERR, "vendorclassid: %m"); return -1; } *ifo->vendorclassid = (uint8_t)s; break; case 'k': ifo->options |= DHCPCD_RELEASE; break; case 'l': if (*arg == '-') { syslog(LOG_ERR, "leasetime must be a positive value"); return -1; } errno = 0; ifo->leasetime = (uint32_t)strtol(arg, NULL, 0); if (errno == EINVAL || errno == ERANGE) { syslog(LOG_ERR, "`%s' out of range", arg); return -1; } break; case 'm': ifo->metric = atoint(arg); if (ifo->metric < 0) { syslog(LOG_ERR, "metric must be a positive value"); return -1; } break; case 'o': if (make_option_mask(ifo->requestmask, arg, 1) != 0) { syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case 'p': ifo->options |= DHCPCD_PERSISTENT; break; case 'q': ifo->options |= DHCPCD_QUIET; break; case 'r': if (parse_addr(&ifo->req_addr, NULL, arg) != 0) return -1; ifo->options |= DHCPCD_REQUEST; ifo->req_mask.s_addr = 0; break; case 's': if (arg && *arg != '\0') { if (parse_addr(&ifo->req_addr, &ifo->req_mask, arg) != 0) return -1; } else { ifo->req_addr.s_addr = 0; ifo->req_mask.s_addr = 0; } ifo->options |= DHCPCD_INFORM | DHCPCD_PERSISTENT; ifo->options &= ~(DHCPCD_ARP | DHCPCD_STATIC); break; case 't': ifo->timeout = atoint(arg); if (ifo->timeout < 0) { syslog(LOG_ERR, "timeout must be a positive value"); return -1; } break; case 'u': s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1; s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2, s, arg); if (s == -1) { syslog(LOG_ERR, "userclass: %m"); return -1; } if (s != 0) { ifo->userclass[ifo->userclass[0] + 1] = s; ifo->userclass[0] += s + 1; } break; case 'v': p = strchr(arg, ','); if (!p || !p[1]) { syslog(LOG_ERR, "invalid vendor format"); return -1; } /* If vendor starts with , then it is not encapsulated */ if (p == arg) { arg++; s = parse_string((char *)ifo->vendor + 1, VENDOR_MAX_LEN, arg); if (s == -1) { syslog(LOG_ERR, "vendor: %m"); return -1; } ifo->vendor[0] = (uint8_t)s; ifo->options |= DHCPCD_VENDORRAW; break; } /* Encapsulated vendor options */ if (ifo->options & DHCPCD_VENDORRAW) { ifo->options &= ~DHCPCD_VENDORRAW; ifo->vendor[0] = 0; } *p = '\0'; i = atoint(arg); arg = p + 1; if (i < 1 || i > 254) { syslog(LOG_ERR, "vendor option should be between" " 1 and 254 inclusive"); return -1; } s = VENDOR_MAX_LEN - ifo->vendor[0] - 2; if (inet_aton(arg, &addr) == 1) { if (s < 6) { s = -1; errno = ENOBUFS; } else memcpy(ifo->vendor + ifo->vendor[0] + 3, &addr.s_addr, sizeof(addr.s_addr)); } else { s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3, s, arg); } if (s == -1) { syslog(LOG_ERR, "vendor: %m"); return -1; } if (s != 0) { ifo->vendor[ifo->vendor[0] + 1] = i; ifo->vendor[ifo->vendor[0] + 2] = s; ifo->vendor[0] += s + 2; } break; case 'w': ifo->options |= DHCPCD_WAITIP; break; case 'y': ifo->reboot = atoint(arg); if (ifo->reboot < 0) { syslog(LOG_ERR, "reboot must be a positive value"); return -1; } break; case 'z': ifav = splitv(&ifac, ifav, arg); break; case 'A': ifo->options &= ~DHCPCD_ARP; /* IPv4LL requires ARP */ ifo->options &= ~DHCPCD_IPV4LL; break; case 'B': ifo->options &= ~DHCPCD_DAEMONISE; break; case 'C': /* Commas to spaces for shell */ while ((p = strchr(arg, ','))) *p = ' '; s = strlen("skip_hooks=") + strlen(arg) + 1; p = xmalloc(sizeof(char) * s); snprintf(p, s, "skip_hooks=%s", arg); add_environ(ifo, p, 0); free(p); break; case 'D': ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; break; case 'E': ifo->options |= DHCPCD_LASTLEASE; break; case 'F': if (!arg) { ifo->fqdn = FQDN_BOTH; break; } if (strcmp(arg, "none") == 0) ifo->fqdn = FQDN_NONE; else if (strcmp(arg, "ptr") == 0) ifo->fqdn = FQDN_PTR; else if (strcmp(arg, "both") == 0) ifo->fqdn = FQDN_BOTH; else if (strcmp(arg, "disable") == 0) ifo->fqdn = FQDN_DISABLE; else { syslog(LOG_ERR, "invalid value `%s' for FQDN", arg); return -1; } break; case 'G': ifo->options &= ~DHCPCD_GATEWAY; break; case 'H': ifo->options |= DHCPCD_XID_HWADDR; break; case 'I': /* Strings have a type of 0 */; ifo->clientid[1] = 0; if (arg) s = parse_string_hwaddr((char *)ifo->clientid + 1, CLIENTID_MAX_LEN, arg, 1); else s = 0; if (s == -1) { syslog(LOG_ERR, "clientid: %m"); return -1; } ifo->options |= DHCPCD_CLIENTID; ifo->clientid[0] = (uint8_t)s; break; case 'J': ifo->options |= DHCPCD_BROADCAST; break; case 'K': ifo->options &= ~DHCPCD_LINK; break; case 'L': ifo->options &= ~DHCPCD_IPV4LL; break; case 'O': if (make_option_mask(ifo->requestmask, arg, -1) != 0 || make_option_mask(ifo->requiremask, arg, -1) != 0 || make_option_mask(ifo->nomask, arg, 1) != 0) { syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case 'Q': if (make_option_mask(ifo->requiremask, arg, 1) != 0 || make_option_mask(ifo->requestmask, arg, 1) != 0) { syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case 'S': p = strchr(arg, '='); if (p == NULL) { syslog(LOG_ERR, "static assignment required"); return -1; } p++; if (strncmp(arg, "ip_address=", strlen("ip_address=")) == 0) { if (parse_addr(&ifo->req_addr, ifo->req_mask.s_addr == 0 ? &ifo->req_mask : NULL, p) != 0) return -1; ifo->options |= DHCPCD_STATIC; ifo->options &= ~DHCPCD_INFORM; } else if (strncmp(arg, "subnet_mask=", strlen("subnet_mask=")) == 0) { if (parse_addr(&ifo->req_mask, NULL, p) != 0) return -1; } else if (strncmp(arg, "routes=", strlen("routes=")) == 0 || strncmp(arg, "static_routes=", strlen("static_routes=")) == 0 || strncmp(arg, "classless_static_routes=", strlen("classless_static_routes=")) == 0 || strncmp(arg, "ms_classless_static_routes=", strlen("ms_classless_static_routes=")) == 0) { np = strchr(p, ' '); if (np == NULL) { syslog(LOG_ERR, "all routes need a gateway"); return -1; } *np++ = '\0'; while (*np == ' ') np++; if (ifo->routes == NULL) { rt = ifo->routes = xmalloc(sizeof(*rt)); } else { rt = ifo->routes; while (rt->next) rt = rt->next; rt->next = xmalloc(sizeof(*rt)); rt = rt->next; } rt->next = NULL; if (parse_addr(&rt->dest, &rt->net, p) == -1 || parse_addr(&rt->gate, NULL, np) == -1) return -1; } else if (strncmp(arg, "routers=", strlen("routers=")) == 0) { if (ifo->routes == NULL) { rt = ifo->routes = xzalloc(sizeof(*rt)); } else { rt = ifo->routes; while (rt->next) rt = rt->next; rt->next = xmalloc(sizeof(*rt)); rt = rt->next; } rt->dest.s_addr = INADDR_ANY; rt->net.s_addr = INADDR_ANY; rt->next = NULL; if (parse_addr(&rt->gate, NULL, p) == -1) return -1; } else { s = 0; if (ifo->config != NULL) { while (ifo->config[s] != NULL) { if (strncmp(ifo->config[s], arg, p - arg) == 0) { free(ifo->config[s]); ifo->config[s] = xstrdup(arg); return 1; } s++; } } ifo->config = xrealloc(ifo->config, sizeof(char *) * (s + 2)); ifo->config[s] = xstrdup(arg); ifo->config[s + 1] = NULL; } break; case 'W': if (parse_addr(&addr, &addr2, arg) != 0) return -1; if (strchr(arg, '/') == NULL) addr2.s_addr = INADDR_BROADCAST; ifo->whitelist = xrealloc(ifo->whitelist, sizeof(in_addr_t) * (ifo->whitelist_len + 2)); ifo->whitelist[ifo->whitelist_len++] = addr.s_addr; ifo->whitelist[ifo->whitelist_len++] = addr2.s_addr; break; case 'X': if (parse_addr(&addr, &addr2, arg) != 0) return -1; if (strchr(arg, '/') == NULL) addr2.s_addr = INADDR_BROADCAST; ifo->blacklist = xrealloc(ifo->blacklist, sizeof(in_addr_t) * (ifo->blacklist_len + 2)); ifo->blacklist[ifo->blacklist_len++] = addr.s_addr; ifo->blacklist[ifo->blacklist_len++] = addr2.s_addr; break; case 'Z': ifdv = splitv(&ifdc, ifdv, arg); break; case O_ARPING: if (parse_addr(&addr, NULL, arg) != 0) return -1; ifo->arping = xrealloc(ifo->arping, sizeof(in_addr_t) * (ifo->arping_len + 1)); ifo->arping[ifo->arping_len++] = addr.s_addr; break; case O_DESTINATION: if (make_option_mask(ifo->dstmask, arg, 2) != 0) { if (errno == EINVAL) syslog(LOG_ERR, "option `%s' does not take" " an IPv4 address", arg); else syslog(LOG_ERR, "unknown option `%s'", arg); return -1; } break; case O_FALLBACK: free(ifo->fallback); ifo->fallback = xstrdup(arg); break; case O_NOIPV6RS: ifo->options &= ~DHCPCD_IPV6RS; break; case O_IPV6_RA_FORK: ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; break; default: return 0; } return 1; }