#include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "uring.h" #include "config.h" #include "server.h" static void eat_whitespace_and_comments(char **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 = *pos; char *end; 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 strtosockaddrs(const char *str, struct list_head *list) { char *tmp; uint16_t port; int r; struct saddr *saddr; if (!str || *str == '\0' || !list) return false; list_init(list); if (*str == '[') { /* IPv6, [a:b:c...h]:p or [*]:p */ debug(DBG_CFG, "attempting to parse IPv6 addr (%s)\n", 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 (!strcmp(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)); } else if (*str == '*') { /* IPv4, *:p */ debug(DBG_CFG, "attempting to parse IPv4 addr (%s)\n", 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); } 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)\n", 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)\n", str); saddr_set_ipv4(saddr, saddr->in4.sin_addr.s_addr, htons(port)); list_add(&saddr->list, list); goto success; } else { xfree(saddr); } struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = 0, .ai_flags = 0, }; struct addrinfo *results, *ai; /* FIXME: This is completely synchronous but getaddrinfo_a is not very ergonomic */ r = getaddrinfo(str, tmp, &hints, &results); if (r != 0) { error("getaddrinfo(%s): %s\n", str, gai_strerror(r)); goto error; } debug(DBG_CFG, "got a hostname:port (%s)\n", str); for (ai = results; ai; ai = ai->ai_next) { saddr = zmalloc(sizeof(*saddr)); if (!saddr) { freeaddrinfo(results); goto error; } switch (ai->ai_family) { case AF_INET: { struct sockaddr_in *in4 = (struct sockaddr_in *)ai->ai_addr; saddr_set_ipv4(saddr, in4->sin_addr.s_addr, htons(port)); list_add(&saddr->list, list); break; } case AF_INET6: { struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); list_add(&saddr->list, list); break; } default: error("getaddrinfo(%s): unknown address family (%i)\n", str, ai->ai_family); xfree(saddr); break; } } freeaddrinfo(results); } else if (strtou16_strict(tmp, &port) == 0) { /* Port */ debug(DBG_CFG, "attempting to parse a port number (%s)\n", str); saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; saddr_set_ipv6(saddr, &in6addr_any, htons(port)); list_add(&saddr->list, list); saddr = zmalloc(sizeof(*saddr)); if (!saddr) goto error; saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); list_add(&saddr->list, list); } else { /* Unknown */ error("unable to parse address: %s\n", str); goto error; } success: if (list_empty(list)) { error("empty address list!?\n"); return false; } else { int i = 0; list_for_each_entry(saddr, list, list) i++; debug(DBG_CFG, "parsed to %i addresses\n", i); } return true; error: if (!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(struct cfg *cfg, const char *filename, char **buf, struct cfg_key_value_map *kvmap, int *rkey, const char **rkeyname, union cfg_value *rvalue) { char *line, *tmp, *key; int i; if (!cfg || !buf || !*buf || !kvmap || !rkey || !rkeyname || !rvalue) die("%s: invalid parameters", filename); eat_whitespace_and_comments(buf); line = get_line(buf); if (!line) return false; debug(DBG_CFG, "%s: parsing config line: %s\n", 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 (strcmp(kvmap[i].key_name, key)) continue; switch (kvmap[i].value_type) { case 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->uint16 = v; break; } case CFG_VAL_TYPE_ADDRS: { if (!strtosockaddrs(tmp, &rvalue->saddrs)) goto error; if (list_empty(&rvalue->saddrs)) { error("empty address list\n"); goto error; } break; } case CFG_VAL_TYPE_BOOL: if (!strcasecmp(tmp, "yes")) rvalue->boolean = true; else if (!strcasecmp(tmp, "no")) rvalue->boolean = false; else { error("invalid boolean value (%s)\n", tmp); goto error; } break; case CFG_VAL_TYPE_INVALID: /* fall through */ default: 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\n", filename, line); *rkey = 0; *rkeyname = NULL; return true; } bool config_parse_header(struct cfg *cfg, const char *filename, const char *title, char **buf) { char *line; if (!cfg || !title || !buf || !*buf) return false; eat_whitespace_and_comments(buf); line = get_line(buf); if (!line) { error("%s: missing header in configuration file\n", filename); return false; } else { char titlehdr[strlen(title) + 3]; sprintf(titlehdr, "[%s]", title); if (strcmp(line, titlehdr)) { error("%s: incorrect header in configuration file\n", filename); return false; } } return true; }