#include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "config-parser.h" #include "server-config-options.h" #include "config.h" static void handle_dns_reply(struct server_config *scfg, enum scfg_keys type, struct saddr *saddr) { switch (type) { case SCFG_KEY_LOCAL: list_add(&saddr->list, &scfg->locals); break; case SCFG_KEY_REMOTE: list_add(&saddr->list, &scfg->remotes); break; case SCFG_KEY_RCON: list_add(&saddr->list, &scfg->rcons); break; default: error("invalid DNS reply type"); break; } } static void scfg_dns_cb(struct dns_async *dns, enum scfg_keys type) { struct server_config *scfg; struct sockaddr_in *in4; struct sockaddr_in6 *in6; struct saddr *saddr; struct addrinfo *results = NULL, *ai; int r; assert_return(dns); scfg = dns->priv; debug(DBG_DNS, "called, dns: %p, name: %s", dns, dns->name); r = gai_error(&dns->gcb); if (r == EAI_INPROGRESS) { /* This shouldn't happen, assume we'll get called again */ error("called with request in progress"); return; } else if (r == EAI_CANCELED) { /* The server must be in the process of going away */ goto out; } else if (r < 0) { error("DNS lookup of %s:%s failed: %s", dns->name, dns->port, gai_strerror(r)); goto out; } results = dns->gcb.ar_result; for (ai = results; ai; ai = ai->ai_next) { saddr = zmalloc(sizeof(*saddr)); if (!saddr) { error("DNS lookup of %s:%s failed: %m", dns->name, dns->port); 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); break; case AF_INET6: in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); break; default: error("getaddrinfo(%s:%s): unknown address family (%i)", dns->name, dns->port, ai->ai_family); xfree(saddr); continue; } handle_dns_reply(scfg, type, saddr); } out: freeaddrinfo(results); list_del(&dns->list); if (dns->notification_cb) dns->notification_cb(scfg, true); xfree(dns); } static void scfg_dns_local_cb(struct dns_async *dns) { scfg_dns_cb(dns, SCFG_KEY_LOCAL); } static void scfg_dns_remote_cb(struct dns_async *dns) { scfg_dns_cb(dns, SCFG_KEY_REMOTE); } static void scfg_dns_rcon_cb(struct dns_async *dns) { scfg_dns_cb(dns, SCFG_KEY_RCON); } static bool handle_dns(struct server_config *scfg, enum scfg_keys type, struct cfg_value *value, notification_cb_t notification_cb) { struct saddr *saddr, *tmp; struct dns_async *dns; assert_return(scfg && type && value, false); switch (value->type) { case CFG_VAL_TYPE_ADDRS: debug(DBG_DNS, "%i: got immediate addrs", type); list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { list_del(&saddr->list); handle_dns_reply(scfg, type, saddr); } return true; case CFG_VAL_TYPE_ASYNC_ADDRS: debug(DBG_DNS, "doing async lookup of DNS record: %p", value->dns_async); dns = value->dns_async; switch (type) { case SCFG_KEY_LOCAL: dns->cb = scfg_dns_local_cb; break; case SCFG_KEY_REMOTE: dns->cb = scfg_dns_remote_cb; break; case SCFG_KEY_RCON: dns->cb = scfg_dns_rcon_cb; break; default: error("invalid DNS lookup type"); return false; } if (!!notification_cb) { error("async DNS lookup received but not requested"); xfree(dns); return false; } dns->priv = scfg; dns->notification_cb = notification_cb; list_add(&dns->list, &scfg->dnslookups); dns->notification_cb(scfg, false); return true; default: return false; } } static inline char tohex(uint8_t val) { static const char hex[] = "0123456789abcdef"; return hex[val & 0x0f]; } /* * Creates an escaped D-Bus object path for a given systemd service * * Escaping rules are documented here: * https://dbus.freedesktop.org/doc/dbus-specification.html * * Essentially, everyting but a-z, A-Z, 0-9 is replaced by _xx where xx is * the hexadecimal value of the character. * * Example: minecraft@world1.service -> minecraft_40world1_2eservice */ static char *systemd_object_path(const char *service) { char *r; char *d; const char *s; assert_return(service && !empty_str(service), NULL); r = zmalloc(STRLEN(SYSTEMD_DBUS_PATH_PREFIX) + strlen(service) * 3 + 1); if (!r) return NULL; memcpy(r, SYSTEMD_DBUS_PATH_PREFIX, STRLEN(SYSTEMD_DBUS_PATH_PREFIX)); d = r + STRLEN(SYSTEMD_DBUS_PATH_PREFIX); for (s = service; *s; s++) { if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9')) { *(d++) = *s; continue; } *(d++) = '_'; *(d++) = tohex(*s >> 4); *(d++) = tohex(*s); } *d = '\0'; return r; } /* FIXME: Add a strict argument */ bool scfg_validate(struct server_config *scfg) { struct saddr *saddr; uint16_t port; if (!scfg) { error("invalid arguments"); return false; } if (!scfg->filename) { error("filename not set"); return false; } if (!list_empty(&scfg->dnslookups)) { error("%s: pending DNS requests", scfg->filename); return false; } if (scfg->stop_method == SERVER_STOP_METHOD_RCON && list_empty(&scfg->rcons)) { error("%s: rcon stop method missing rcon address", scfg->filename); return false; } if (scfg->stop_method == SERVER_STOP_METHOD_RCON && !scfg->rcon_password) { error("%s: rcon stop method missing rcon password", scfg->filename); return false; } if ((scfg->start_method == SERVER_START_METHOD_SYSTEMD || scfg->stop_method == SERVER_STOP_METHOD_SYSTEMD)) { if (!scfg->systemd_service || !scfg->systemd_obj) { error("%s: systemd start/stop method missing " "systemd service", scfg->filename); return false; } else { if (scfg->systemd_service || scfg->systemd_obj) { error("%s: systemd service set but not used", scfg->filename); return false; } } } if (scfg->idle_timeout > 0 && scfg->stop_method == SERVER_STOP_METHOD_UNDEFINED) { error("%s: idle_timeout set but missing stop method", scfg->filename); return false; } switch (scfg->type) { case SERVER_TYPE_ANNOUNCE: if (scfg->announce_port == 0) { error("%s: missing announce port", scfg->filename); return false; } if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) { error("%s: can't set start_method for announce server", scfg->filename); return false; } if (scfg->start_exec) { error("%s: can't set start_exec for announce server", scfg->filename); return false; } if (!list_empty(&scfg->locals)) { error("%s: can't set local addresses for announce server", scfg->filename); return false; } if (!list_empty(&scfg->remotes)) { error("%s: can't set remote addresses for announce server", scfg->filename); return false; } break; case SERVER_TYPE_PROXY: if (scfg->announce_port == 0) { error("%s: can't set announce port for proxy server", scfg->filename); return false; } if (list_empty(&scfg->locals)) { error("%s: missing local addresses for proxy server", scfg->filename); return false; } if (list_empty(&scfg->remotes)) { error("%s: missing remote addresses for proxy server", scfg->filename); return false; } list_for_each_entry(saddr, &scfg->locals, list) { port = saddr_port(saddr); if (port == 0) { error("%s: invalid local port", scfg->filename); return false; } if (scfg->announce_port == 0) scfg->announce_port = port; else if (scfg->announce_port != port) { error("%s: multiple local ports", scfg->filename); return false; } } if (scfg->announce_port == 0) { error("%s: can't determine which port to announce", scfg->filename); return false; } break; default: error("%s: can't determine server type", scfg->filename); return false; } return true; } #define INVALID(fmt, ...) \ do { \ verbose("%s: " fmt, scfg->filename __VA_OPT__(,) __VA_ARGS__); \ valid = false; \ } while(0) bool scfg_parse(struct server_config *scfg, char *buf, notification_cb_t notification_cb) { unsigned lineno; char *pos; bool valid = true; assert_return(scfg && buf, false); pos = buf; if (!config_parse_header(SERVER_CFG_HEADER, &pos, &lineno)) { INVALID("missing/invalid header"); goto out; } while (true) { int key; const char *keyname; struct cfg_value value; if (!config_parse_line(scfg->filename, &pos, scfg_key_map, &key, &keyname, &value, !!notification_cb, &lineno)) break; /* FIXME: Improve error reporting */ if (key == SCFG_KEY_INVALID) { INVALID("invalid line in config file"); continue; } debug(DBG_CFG, "%s: key %s", scfg->filename, keyname); switch (key) { case SCFG_KEY_TYPE: if (scfg->type != SERVER_TYPE_UNDEFINED) INVALID("type defined multiple times"); else if (streq(value.str, "proxy")) scfg->type = SERVER_TYPE_PROXY; else if (streq(value.str, "announce")) scfg->type = SERVER_TYPE_ANNOUNCE; else INVALID("unknown type: %s", value.str); break; case SCFG_KEY_NAME: if (scfg->pretty_name) INVALID("name defined multiple times"); else if (empty_str(value.str)) INVALID("name is an empty string"); else if (!(scfg->pretty_name = xstrdup(value.str))) INVALID("strdup: %m"); break; case SCFG_KEY_PORT: if (scfg->announce_port != 0) INVALID("port defined multiple times"); else scfg->announce_port = value.uint16; break; case SCFG_KEY_LOCAL: if (!handle_dns(scfg, SCFG_KEY_LOCAL, &value, notification_cb)) INVALID("handle_dns failed"); break; case SCFG_KEY_REMOTE: if (!handle_dns(scfg, SCFG_KEY_REMOTE, &value, notification_cb)) INVALID("handle_dns failed"); break; case SCFG_KEY_IDLE_TIMEOUT: if (scfg->idle_timeout != 0) INVALID("idle_timeout already set"); else scfg->idle_timeout = value.uint16; break; case SCFG_KEY_STOP_METHOD: if (scfg->stop_method != SERVER_STOP_METHOD_UNDEFINED) INVALID("stop method defined multiple times"); else if (streq(value.str, "exec")) scfg->stop_method = SERVER_STOP_METHOD_EXEC; else if (streq(value.str, "rcon")) scfg->stop_method = SERVER_STOP_METHOD_RCON; else if (streq(value.str, "systemd")) scfg->stop_method = SERVER_STOP_METHOD_SYSTEMD; else INVALID("unknown stop method: %s", value.str); break; case SCFG_KEY_START_METHOD: if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) INVALID("start method defined multiple times"); else if (streq(value.str, "exec")) scfg->start_method = SERVER_START_METHOD_EXEC; else if (streq(value.str, "systemd")) scfg->start_method = SERVER_START_METHOD_SYSTEMD; else INVALID("unknown start method: %s", value.str); break; case SCFG_KEY_STOP_EXEC: if (scfg->stop_exec) INVALID("stop exec defined multiple times"); else if (!(scfg->stop_exec = xstrdup(value.str))) INVALID("strdup: %m"); break; case SCFG_KEY_START_EXEC: if (scfg->start_exec) INVALID("start exec defined multiple times"); else if (!(scfg->start_exec = xstrdup(value.str))) INVALID("strdup: %m"); break; case SCFG_KEY_RCON: if (!handle_dns(scfg, SCFG_KEY_RCON, &value, notification_cb)) INVALID("handle_dns failed"); break; case SCFG_KEY_RCON_PASSWORD: if (scfg->rcon_password) INVALID("rcon_password defined multiple times"); else if (!(scfg->rcon_password = xstrdup(value.str))) INVALID("strdup: %m"); break; case SCFG_KEY_SYSTEMD_SERVICE: if (scfg->systemd_service) INVALID("systemd_service defined multiple times"); else { const char *suffix; char *tmp; suffix = strrchr(value.str, '.'); if (!suffix || !streq(suffix, ".service")) { tmp = zmalloc(strlen(value.str) + STRLEN(".service") + 1); if (tmp) sprintf(tmp, "%s.service", value.str); } else tmp = xstrdup(value.str); if (!tmp) { INVALID("malloc/strdup: %m"); break; } scfg->systemd_service = tmp; scfg->systemd_obj = systemd_object_path(scfg->systemd_service); if (!scfg->systemd_obj) INVALID("object_path: %m"); } break; case SCFG_KEY_INVALID: default: INVALID("invalid line"); break; } } out: return valid; } void scfg_delete(struct server_config *scfg) { struct saddr *saddr, *tmp; struct dns_async *dns; xfree(scfg->filename); xfree(scfg->pretty_name); xfree(scfg->stop_exec); xfree(scfg->start_exec); /* FIXME: use free_password */ xfree(scfg->rcon_password); xfree(scfg->systemd_service); xfree(scfg->systemd_obj); list_for_each_entry_safe(saddr, tmp, &scfg->remotes, list) { list_del(&saddr->list); xfree(saddr); } list_for_each_entry_safe(saddr, tmp, &scfg->rcons, list) { list_del(&saddr->list); xfree(saddr); } list_for_each_entry_safe(saddr, tmp, &scfg->locals, list) { list_del(&saddr->list); xfree(saddr); } list_for_each_entry(dns, &scfg->dnslookups, list) gai_cancel(&dns->gcb); } bool scfg_init(struct server_config *scfg, const char *filename) { assert_return(scfg, false); if (filename) { scfg->filename = xstrdup(filename); if (!scfg->filename) { error("strdup: %m"); return false; } } scfg->type = SERVER_TYPE_UNDEFINED; scfg->pretty_name = NULL; scfg->announce_port = 0; scfg->idle_timeout = 0; scfg->stop_method = SERVER_STOP_METHOD_UNDEFINED; scfg->start_method = SERVER_START_METHOD_UNDEFINED; scfg->stop_exec = NULL; scfg->start_exec = NULL; scfg->rcon_password = NULL; scfg->systemd_service = NULL; scfg->systemd_obj = NULL; INIT_LIST_HEAD(&scfg->remotes); INIT_LIST_HEAD(&scfg->locals); INIT_LIST_HEAD(&scfg->rcons); INIT_LIST_HEAD(&scfg->dnslookups); return true; } static void eat_whitespace_and_comments(char **pos, unsigned *lineno) { assert_return(pos && *pos && lineno); while (true) { while (**pos == ' ' || **pos == '\f' || **pos == '\r' || **pos == '\t' || **pos == '\v') (*pos)++; if (**pos == '\n') { (*lineno)++; (*pos)++; continue; } if (**pos != '#') return; while (**pos != '\n' && **pos != '\0') (*pos)++; } } static char *get_line(char **pos, unsigned *lineno) { char *begin, *end; assert_return(pos && *pos && lineno, NULL); (*lineno)++; eat_whitespace_and_comments(pos, lineno); begin = *pos; 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; /* FIXME: parts can be shared with the async cb? */ 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; INIT_LIST_HEAD(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, unsigned *lineno) { char *line, *pos, *key, *value; size_t linelen; int i; assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue && lineno, false); line = get_line(buf, lineno); if (!line) return false; debug(DBG_CFG, "%s: parsing cfg line %u: %s", filename, *lineno, line); linelen = strlen(line); key = line; pos = memchr(line, '=', linelen); if (!pos) goto error; value = pos + 1; *pos = '\0'; while (isspace(*(--pos))) *pos = '\0'; while (isspace(*value)) value++; if (*value == '\0') goto error; for (i = 0; kvmap[i].key_name; i++) { if (streq(kvmap[i].key_name, key)) break; } if (!kvmap[i].key_name) goto error; switch (kvmap[i].value_type) { case CFG_VAL_TYPE_STRING: rvalue->type = CFG_VAL_TYPE_STRING; rvalue->str = value; break; case CFG_VAL_TYPE_UINT16: { uint16_t v; if (strtou16_strict(value, &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(value, 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(value, "yes") || strcaseeq(value, "true")) { rvalue->type = CFG_VAL_TYPE_BOOL; rvalue->boolean = true; } else if (strcaseeq(value, "no") || strcaseeq(value, "false")) { rvalue->type = CFG_VAL_TYPE_BOOL; rvalue->boolean = false; } else { error("invalid boolean value (%s)", value); 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: error("%s: invalid config line: %u:%s", filename, *lineno, line); rvalue->type = CFG_VAL_TYPE_INVALID; *rkey = 0; *rkeyname = NULL; return true; } bool config_parse_header(const char *title, char **buf, unsigned *lineno) { char *line; assert_return(!empty_str(title) && buf && *buf && lineno, false); *lineno = 0; line = get_line(buf, lineno); 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; }