#include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "config-parser.h" #include "config.h" static void eat_whitespace_and_comments(char **pos) { assert_return(pos && *pos); while (true) { while (isspace(**pos)) (*pos)++; if (**pos != '#') return; while (**pos != '\r' && **pos != '\n' && **pos != '\0') (*pos)++; } } static char * get_line(char **pos) { char *begin, *end; assert_return(pos && *pos, NULL); eat_whitespace_and_comments(pos); begin = *pos; while (isspace(*begin)) begin++; if (*begin == '\0') return NULL; end = begin; while (*end != '\n' && *end != '\0') end++; if (*end == '\0') *pos = end; else *pos = end + 1; while (isspace(*end)) { *end = '\0'; end--; } return begin; } static bool dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, unsigned *naddrs, bool async) { struct sockaddr_in *in4; struct sockaddr_in6 *in6; struct dns_async tmp; struct dns_async *dns; int mode = async ? GAI_NOWAIT : GAI_WAIT; struct addrinfo *results = NULL, *ai; struct saddr *saddr = NULL; bool rv = false; int r; assert_return(!empty_str(name) && strlen(name) < sizeof(dns->name) && port > 0 && rvalue, false); if (async) { rvalue->type = CFG_VAL_TYPE_ASYNC_ADDRS; rvalue->dns_async = NULL; dns = zmalloc(sizeof(*dns)); if (!dns) { error("async DNS lookup of %s failed: %m", name); goto out; } debug(DBG_DNS, "doing async DNS lookup of %s: %p", name, dns); } else { memset(&tmp, 0, sizeof(tmp)); dns = &tmp; debug(DBG_DNS, "doing sync DNS lookup of %s", name); } sprintf(dns->name, "%s", name); sprintf(dns->port, "%" PRIu16, port); dns->req.ai_family = AF_UNSPEC; dns->req.ai_socktype = SOCK_STREAM; dns->req.ai_protocol = 0; dns->req.ai_flags = AI_NUMERICSERV; dns->sev.sigev_notify = SIGEV_SIGNAL; dns->sev.sigev_signo = SIGUSR1; dns->sev.sigev_value.sival_ptr = dns; dns->gcb.ar_name = dns->name; dns->gcb.ar_service = dns->port; dns->gcb.ar_request = &dns->req; struct gaicb *gcbs[] = { &dns->gcb }; r = getaddrinfo_a(mode, gcbs, ARRAY_SIZE(gcbs), &dns->sev); if (r != 0) { error("getaddrinfo(%s:%" PRIu16 "): %s", name, port, gai_strerror(r)); goto out; } if (async) { rvalue->dns_async = dns; rv = true; goto out; } results = dns->gcb.ar_result; for (ai = results; ai; ai = ai->ai_next) { saddr = zmalloc(sizeof(*saddr)); if (!saddr) { error("sync DNS lookup of %s failed: %m", name); goto out; } switch (ai->ai_family) { case AF_INET: in4 = (struct sockaddr_in *)ai->ai_addr; saddr_set_ipv4(saddr, in4->sin_addr.s_addr, in4->sin_port); debug(DBG_CFG, "addrstr: %s", saddr->addrstr); list_add(&saddr->list, &rvalue->saddrs); (*naddrs)++; break; case AF_INET6: in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); debug(DBG_CFG, "addrstr: %s", saddr->addrstr); list_add(&saddr->list, &rvalue->saddrs); (*naddrs)++; break; default: error("getaddrinfo(%s:%s): unknown address family (%i)", dns->name, dns->port, ai->ai_family); xfree(saddr); break; } } rv = true; out: freeaddrinfo(results); return rv; } bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) { struct saddr *saddr; uint16_t port; char *tmp; struct list_head *list; unsigned naddrs = 0; assert_return(!empty_str(str) && rvalue, false); rvalue->type = CFG_VAL_TYPE_ADDRS; list = &rvalue->saddrs; list_init(list); if (*str == '[') { /* IPv6, [a:b:c...h]:p or [*]:p */ debug(DBG_CFG, "attempting to parse IPv6 addr (%s)", str); str++; tmp = strchr(str, ']'); if (!tmp) goto error; *tmp = '\0'; saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; /* early list_add to make sure saddr is free():d on error */ list_add(&saddr->list, list); if (streq(str, "*")) saddr->in6.sin6_addr = in6addr_any; else if (inet_pton(AF_INET6, str, &saddr->in6.sin6_addr) <= 0) goto error; tmp++; if (*tmp != ':') goto error; tmp++; if (strtou16_strict(tmp, &port) < 0) goto error; saddr_set_ipv6(saddr, NULL, htons(port)); naddrs++; } else if (*str == '*') { /* IPv4, *:p */ debug(DBG_CFG, "attempting to parse IPv4 addr (%s)", str); str++; if (*str != ':') goto error; str++; if (strtou16_strict(str, &port) < 0) goto error; saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); list_add(&saddr->list, list); naddrs++; } else if ((tmp = strchr(str, ':'))) { /* IPv4, a.b.c.d:p or IPv4/6 hostname:p */ debug(DBG_CFG, "attempting to parse IPv4 addr or hostname (%s)", str); *tmp = '\0'; tmp++; if (strtou16_strict(tmp, &port) < 0) goto error; saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; if (inet_pton(AF_INET, str, &saddr->in4.sin_addr) > 0) { debug(DBG_CFG, "got an IPv4:port (%s:%" PRIu16 ")", str, port); saddr_set_ipv4(saddr, saddr->in4.sin_addr.s_addr, htons(port)); list_add(&saddr->list, list); naddrs++; goto success; } xfree(saddr); debug(DBG_CFG, "maybe got a hostname:port (%s:%" PRIu16 ")", str, port); if (!dnslookup(str, port, rvalue, &naddrs, async)) goto error; } else if (strtou16_strict(tmp, &port) == 0) { /* Port */ debug(DBG_CFG, "attempting to parse a port number (%s)", str); saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; saddr_set_ipv6(saddr, &in6addr_any, htons(port)); list_add(&saddr->list, list); naddrs++; saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); list_add(&saddr->list, list); naddrs++; } else { /* Unknown */ goto error; } success: switch (rvalue->type) { case CFG_VAL_TYPE_ADDRS: if (list_empty(list) || naddrs == 0) { error("empty address list"); return false; } debug(DBG_CFG, "parsed to %u addresses", naddrs); return true; case CFG_VAL_TYPE_ASYNC_ADDRS: debug(DBG_CFG, "looking up address asynchronously"); return true; default: error("invalid rvalue type"); rvalue->type = CFG_VAL_TYPE_INVALID; break; } error: if (rvalue->type == CFG_VAL_TYPE_ADDRS && !list_empty(list)) { struct saddr *tmp; list_for_each_entry_safe(saddr, tmp, list, list) { list_del(&saddr->list); xfree(saddr); } } return false; } /* Returns true if there's data left to parse in buf */ bool config_parse_line(const char *filename, char **buf, struct cfg_key_value_map *kvmap, int *rkey, const char **rkeyname, struct cfg_value *rvalue, bool async_dns) { char *line, *tmp, *key; int i; assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue, false); line = get_line(buf); if (!line) return false; debug(DBG_CFG, "%s: parsing config line: %s", filename, line); tmp = line; while (isspace(*tmp)) tmp++; if (*tmp == '\0') goto error; key = tmp; while (*tmp != '\0' && !isspace(*tmp)) tmp++; if (*tmp == '\0') goto error; *tmp = '\0'; tmp++; while (isspace(*tmp)) tmp++; if (*tmp != '=') goto error; tmp++; while (isspace(*tmp)) tmp++; if (*tmp == '\0') goto error; for (i = 0; kvmap[i].key_name; i++) { if (!streq(kvmap[i].key_name, key)) continue; switch (kvmap[i].value_type) { case CFG_VAL_TYPE_STRING: rvalue->type = CFG_VAL_TYPE_STRING; rvalue->str = tmp; break; case CFG_VAL_TYPE_UINT16: { uint16_t v; if (strtou16_strict(tmp, &v) < 0) goto error; rvalue->type = CFG_VAL_TYPE_UINT16; rvalue->uint16 = v; break; } case CFG_VAL_TYPE_ASYNC_ADDRS: error("CFG_VAL_TYPE_ASYNC_ADDRS is a return value"); _fallthrough_; case CFG_VAL_TYPE_ADDRS: if (!strtosockaddrs(tmp, rvalue, async_dns)) goto error; switch (rvalue->type) { case CFG_VAL_TYPE_ADDRS: if (list_empty(&rvalue->saddrs)) { error("empty address list"); goto error; } break; case CFG_VAL_TYPE_ASYNC_ADDRS: if (!async_dns) { error("unexpected async dns result"); goto error; } if (!rvalue->dns_async) { error("dns_async not set"); goto error; } break; default: error("invalid type returned from strtosockaddrs"); goto error; } break; case CFG_VAL_TYPE_BOOL: if (strcaseeq(tmp, "yes") || strcaseeq(tmp, "true")) { rvalue->type = CFG_VAL_TYPE_BOOL; rvalue->boolean = true; } else if (strcaseeq(tmp, "no") || strcaseeq(tmp, "false")) { rvalue->type = CFG_VAL_TYPE_BOOL; rvalue->boolean = false; } else { error("invalid boolean value (%s)", tmp); goto error; } break; case CFG_VAL_TYPE_INVALID: _fallthrough_; default: goto error; } /* sanity check */ if ((rvalue->type != kvmap[i].value_type) && ((kvmap[i].value_type != CFG_VAL_TYPE_ASYNC_ADDRS) && (rvalue->type != CFG_VAL_TYPE_ADDRS))) { error("rvalue->type != kvmap->type"); goto error; } *rkey = kvmap[i].key_value; *rkeyname = kvmap[i].key_name; return true; } error: /* FIXME: the line is already mangled here, a line number would be nice */ error("%s: invalid config line: %s", filename, line); rvalue->type = CFG_VAL_TYPE_INVALID; *rkey = 0; *rkeyname = NULL; return true; } bool config_parse_header(const char *title, char **buf) { char *line; assert_return(!empty_str(title) && buf && *buf, false); line = get_line(buf); if (line) { char titlehdr[strlen(title) + 3]; sprintf(titlehdr, "[%s]", title); if (streq(line, titlehdr)) return true; } return false; } bool is_valid_server_config_filename(struct dirent *dent, const char *filename) { const char *suffix; assert_return(!(dent && filename) && !(!dent && !filename), false); /* Maybe accept DT_LNK? */ if (dent) { switch (dent->d_type) { case DT_UNKNOWN: _fallthrough_; case DT_REG: break; default: return false; } filename = dent->d_name; } if (empty_str(filename)) return false; if (filename[0] == '.') return false; if ((suffix = strrchr(filename, '.')) == NULL) return false; if (!streq(suffix, "." SERVER_CONFIG_FILE_SUFFIX)) return false; return true; }