/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "config-parser.h" #include "server-config-options.h" #include "server-properties-options.h" static bool handle_addrinfo_results(struct addrinfo *results, struct list_head *target_list, unsigned *naddrs) { struct sockaddr_in *in4; struct sockaddr_in6 *in6; struct saddr *saddr; struct addrinfo *ai; bool rv = true; for (ai = results; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; saddr = zmalloc(sizeof(*saddr)); if (!saddr) { error("DNS lookup failed: %m"); rv = false; } 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); (*naddrs)++; break; case AF_INET6: in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); break; (*naddrs)++; } list_add(&saddr->list, target_list); } freeaddrinfo(results); return rv; } void scfg_async_dns_update(struct server_config *scfg) { struct dns_async *dns, *tmp; unsigned naddrs = 0; bool alldone = true; int r; assert_return(scfg); debug(DBG_DNS, "called, scfg: %p", scfg); list_for_each_entry(dns, &scfg->dnslookups, list) { if (!dns->pending) continue; r = gai_error(&dns->gcb); switch (r) { case EAI_INPROGRESS: /* Assume we'll get called again */ alldone = false; break; case EAI_CANCELED: /* The server must be in the process of going away */ dns->pending = false; break; case 0: /* Success */ dns->pending = false; handle_addrinfo_results(dns->gcb.ar_result, dns->target_list, &naddrs); debug(DBG_DNS, "async DNS added %u records", naddrs); break; default: /* Misc failures */ dns->pending = false; error("DNS lookup of %s:%s failed: %s", dns->name, dns->port, gai_strerror(r)); break; } } if (!alldone) return; list_for_each_entry_safe(dns, tmp, &scfg->dnslookups, list) { list_del(&dns->list); xfree(dns); } xfree(scfg->gcbs); scfg->gcbs = NULL; } bool scfg_async_dns_start(struct server_config *scfg) { struct dns_async *dns; unsigned ndns = 0; int r; assert_return(scfg, false); list_for_each_entry(dns, &scfg->dnslookups, list) ndns++; scfg->gcbs = zmalloc(sizeof(struct gaicb) * ndns); if (!scfg->gcbs) { error("failed to allocate gcbs: %m"); return false; } ndns = 0; list_for_each_entry(dns, &scfg->dnslookups, list) { scfg->gcbs[ndns++] = &dns->gcb; dns->pending = true; } r = getaddrinfo_a(GAI_NOWAIT, scfg->gcbs, ndns, &scfg->dns_sev); if (r != 0) { error("getaddrinfo_a(%u): %s", ndns, gai_strerror(r)); /* FIXME: More error handling */ } return true; } static void scfg_queue_dns(struct server_config *scfg, struct cfg_value *value, struct list_head *target_list, bool any_ok) { struct saddr *saddr, *tmp; struct dns_async *dns; switch (value->type) { case CFG_VAL_TYPE_ADDRS: debug(DBG_DNS, "got sync results"); list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { list_del(&saddr->list); if (!any_ok) saddr_any_to_loopback(saddr); list_add(&saddr->list, target_list); } break; case CFG_VAL_TYPE_ASYNC_ADDRS: dns = value->dns_async; debug(DBG_DNS, "enqueing async lookup of %s:%s (%p)", dns->name, dns->port, dns); dns->target_list = target_list; list_add(&dns->list, &scfg->dnslookups); break; default: error("Unexpected value type"); break; } } #define ERROR(msg) do { *error = (msg); return false; } while (0) bool scfg_validate(struct server_config *scfg, const char **error) { struct saddr *saddr; uint16_t port; assert_return(scfg && error, false); if (!scfg) ERROR("invalid arguments"); if (!scfg->filename) ERROR("filename not set"); if (!list_empty(&scfg->dnslookups)) ERROR("pending DNS requests"); if (scfg->stop_method == SERVER_STOP_METHOD_RCON && list_empty(&scfg->rcons)) ERROR("rcon stop method missing rcon address"); if (scfg->stop_method == SERVER_STOP_METHOD_RCON && !scfg->rcon_password) ERROR("rcon stop method missing rcon password"); if ((scfg->start_method == SERVER_START_METHOD_SYSTEMD || scfg->stop_method == SERVER_STOP_METHOD_SYSTEMD)) { if (!scfg->systemd_service || !scfg->systemd_obj) ERROR("systemd start/stop missing service"); } else { if (scfg->systemd_service || scfg->systemd_obj) ERROR("systemd service set but not used"); } if (scfg->idle_timeout > 0 && scfg->stop_method == SERVER_STOP_METHOD_UNDEFINED) ERROR("idle_timeout set but missing stop method"); switch (scfg->type) { case SERVER_TYPE_ANNOUNCE: if (scfg->announce == SERVER_ANNOUNCE_NEVER) ERROR("announce policy never with an announce server"); if (scfg->announce_port == 0) ERROR("missing announce port"); if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) ERROR("can't set start_method for announce server"); if (scfg->start_exec) ERROR("can't set start_exec for announce server"); if (!list_empty(&scfg->locals)) ERROR("can't set local addresses for announce server"); if (!list_empty(&scfg->remotes)) ERROR("can't set remote addresses for announce server"); break; case SERVER_TYPE_PROXY: if (scfg->announce_port != 0) ERROR("can't set announce port for proxy server"); if (list_empty(&scfg->locals)) ERROR("missing local addresses for proxy server"); if (list_empty(&scfg->remotes)) ERROR("missing remote addresses for proxy server"); list_for_each_entry(saddr, &scfg->locals, list) { port = saddr_port(saddr); if (port == 0) ERROR("invalid local port"); if (scfg->announce_port == 0) scfg->announce_port = port; else if (scfg->announce_port != port) ERROR("multiple local ports"); } if (scfg->announce_port == 0) ERROR("can't determine which port to announce"); break; default: ERROR("can't determine server type"); } return true; } 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; } void sprop_parse(struct server_config *scfg, char *buf, unsigned *lineno, const char **error) { char *pos = buf; assert_return(scfg && buf && lineno && error); *lineno = 0; while (true) { int key; const char *keyname; struct cfg_value value; if (!config_parse_line("server.properties", &pos, sprop_key_map, &key, &keyname, &value, false, lineno, error)) break; switch (key) { case SPROP_KEY_SERVER_PORT: debug(DBG_CFG, "found remote server port in " "server.properties"); if (!list_empty(&scfg->remotes)) { error("mc server address set both in %s and " "server.properties", scfg->filename); break; } scfg_queue_dns(scfg, &value, &scfg->remotes, false); break; case SPROP_KEY_RCON_PORT: debug(DBG_CFG, "found rcon port in " "server.properties"); if (!list_empty(&scfg->rcons)) { error("rcon address set both in %s and " "server.properties", scfg->filename); break; } scfg_queue_dns(scfg, &value, &scfg->rcons, false); break; case SPROP_KEY_RCON_PASSWORD: debug(DBG_CFG, "found rcon password in " "server.properties"); if (scfg->rcon_password) { error("rcon password set both in %s and " "server.properties (%smatching)", scfg->filename, streq(scfg->rcon_password, value.str) ? "" : "not"); break; } scfg->rcon_password = xstrdup(value.str); break; case SPROP_KEY_INVALID: _fallthrough_; default: break; } } } bool scfg_parse(struct server_config *scfg, char *buf, bool async, unsigned *lineno, const char **error) { char *pos = buf; assert_return(scfg && buf && lineno && error, false); *lineno = 0; if (!config_parse_header(SERVER_CFG_HEADER, &pos, lineno)) ERROR("missing/invalid header"); while (true) { int key; const char *keyname; struct cfg_value value; if (!config_parse_line(scfg->filename, &pos, scfg_key_map, &key, &keyname, &value, async, lineno, error)) break; if (key == SCFG_KEY_INVALID) return false; debug(DBG_CFG, "%s: key %s", scfg->filename, keyname); switch (key) { case SCFG_KEY_TYPE: if (scfg->type != SERVER_TYPE_UNDEFINED) ERROR("server 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 ERROR("unknown server type"); break; case SCFG_KEY_NAME: if (scfg->pretty_name) ERROR("server name defined multiple times"); else if (empty_str(value.str)) ERROR("server name is an empty string"); else if (!(scfg->pretty_name = xstrdup(value.str))) ERROR("strdup failure"); break; case SCFG_KEY_ANNOUNCE: if (scfg->announce != SERVER_ANNOUNCE_UNDEFINED) ERROR("announce policy defined multiple times"); else if (streq(value.str, "always")) scfg->announce = SERVER_ANNOUNCE_ALWAYS; else if (streq(value.str, "never")) scfg->announce = SERVER_ANNOUNCE_NEVER; else if (streq(value.str, "when-running")) scfg->announce = SERVER_ANNOUNCE_WHEN_RUNNING; else ERROR("unknown announce policy"); break; case SCFG_KEY_PORT: if (scfg->announce_port != 0) ERROR("port defined multiple times"); else scfg->announce_port = value.uint16; break; case SCFG_KEY_LOCAL: scfg_queue_dns(scfg, &value, &scfg->locals, true); break; case SCFG_KEY_REMOTE: scfg_queue_dns(scfg, &value, &scfg->remotes, false); break; case SCFG_KEY_IDLE_TIMEOUT: if (scfg->idle_timeout != 0) ERROR("idle_timeout already set"); else scfg->idle_timeout = value.uint16; break; case SCFG_KEY_STOP_METHOD: if (scfg->stop_method != SERVER_STOP_METHOD_UNDEFINED) ERROR("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 ERROR("unknown stop method"); break; case SCFG_KEY_START_METHOD: if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) ERROR("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 ERROR("unknown start method"); break; case SCFG_KEY_STOP_EXEC: if (scfg->stop_exec) ERROR("stop exec defined multiple times"); else if (!(scfg->stop_exec = xstrdup(value.str))) ERROR("strdup failure"); break; case SCFG_KEY_START_EXEC: if (scfg->start_exec) ERROR("start exec defined multiple times"); else if (!(scfg->start_exec = xstrdup(value.str))) ERROR("strdup failure"); break; case SCFG_KEY_RCON: scfg_queue_dns(scfg, &value, &scfg->rcons, false); break; case SCFG_KEY_RCON_PASSWORD: if (scfg->rcon_password) ERROR("rcon_password defined multiple times"); else if (!(scfg->rcon_password = xstrdup(value.str))) ERROR("strdup failure"); break; case SCFG_KEY_SYSTEMD_SERVICE: if (scfg->systemd_service) ERROR("systemd service defined multiple times"); const char *suffix = strrchr(value.str, '.'); if (!suffix || !streq(suffix, ".service")) scfg->systemd_service = xsprintf(NULL, "%s.service", value.str); else scfg->systemd_service = xstrdup(value.str); if (!scfg->systemd_service) ERROR("malloc/strdup failure"); break; case SCFG_KEY_INVALID: _fallthrough_; default: ERROR("invalid line"); break; } } if (!scfg->systemd_service && (scfg->stop_method == SERVER_STOP_METHOD_SYSTEMD || scfg->start_method == SERVER_START_METHOD_SYSTEMD)) { scfg->systemd_service = xsprintf(NULL, "minecserver@%s.service", scfg->name); if (!scfg->systemd_service) ERROR("failed to create systemd_service string"); } if (scfg->systemd_service && !scfg->systemd_obj) { scfg->systemd_obj = systemd_object_path(scfg->systemd_service); if (!scfg->systemd_obj) ERROR("failed to create object_path"); } return true; } 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); free_password(&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) { if (dns->pending) gai_cancel(&dns->gcb); } } bool scfg_init(struct server_config *scfg, const char *filename) { assert_return(scfg, false); if (filename) { const char *suffix; suffix = strrchr(filename, '.'); if (!suffix || suffix == filename) { error("invalid filename: %s", filename); return false; } scfg->name = xstrndup(filename, suffix - filename); if (!scfg->name) { error("xstrndup: %m"); return false; } scfg->filename = xstrdup(filename); if (!scfg->filename) { error("strdup: %m"); xfree(scfg->name); scfg->name = NULL; return false; } } else { scfg->name = NULL; scfg->filename = NULL; } scfg->type = SERVER_TYPE_UNDEFINED; scfg->pretty_name = NULL; scfg->announce = SERVER_ANNOUNCE_UNDEFINED; 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); scfg->gcbs = NULL; scfg->dns_sev.sigev_notify = SIGEV_SIGNAL; scfg->dns_sev.sigev_signo = SIGUSR1; scfg->dns_sev.sigev_value.sival_ptr = scfg; 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 cfg_dns_lookup(const char *name, uint16_t port, struct cfg_value *rvalue, unsigned *naddrs, bool async) { struct dns_async tmp; struct dns_async *dns; struct addrinfo *results = NULL; 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); return false; } debug(DBG_DNS, "doing async DNS lookup of %s: %p", name, dns); dns->pending = false; dns->gcb.ar_name = dns->name; dns->gcb.ar_service = dns->port; dns->gcb.ar_request = &dns->req; rvalue->dns_async = 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; if (async) return true; r = getaddrinfo(dns->name, dns->port, &dns->req, &results); if (r != 0) { error("getaddrinfo(%s:%s): %s", dns->name, dns->port, gai_strerror(r)); return false; } return handle_addrinfo_results(results, &rvalue->saddrs, naddrs); } 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, htonl(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 (!cfg_dns_lookup(str, port, rvalue, &naddrs, async)) goto error; } else if (strtou16_strict(str, &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, htonl(INADDR_ANY), htons(port)); list_add(&saddr->list, list); naddrs++; } else { /* Unknown */ debug(DBG_CFG, "cannot parse str (%s)", str); 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, const char **error) { char *line, *pos, *key, *value; size_t linelen; int i; assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue && lineno && error, false); *error = NULL; 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)) { *error = "Failed to parse address/port line"; 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"; 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_ADDRS) && (rvalue->type != CFG_VAL_TYPE_ASYNC_ADDRS))) { error("rvalue->type != kvmap->type (%i %i)", rvalue->type, kvmap[i].value_type); goto error; } *rkey = kvmap[i].key_value; *rkeyname = kvmap[i].key_name; return true; error: if (!*error) *error = "invalid 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); 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 config_valid_server_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; }