#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 == '#') { while (**pos != '\r' && **pos != '\n' && **pos != '\0') (*pos)++; continue; } return; } } 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 sockaddr_in46 *addr; 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 out; *tmp = '\0'; addr = zmalloc(sizeof(*addr)); if (!addr) goto out; list_add(&addr->list, list); if (!strcmp(str, "*")) addr->in6.sin6_addr = in6addr_any; else if (inet_pton(AF_INET6, str, &addr->in6.sin6_addr) <= 0) goto out; tmp++; if (*tmp != ':') goto out; tmp++; if (strtou16_strict(tmp, &port) < 0) goto out; addr->in6.sin6_family = AF_INET6; addr->in6.sin6_port = htons(port); addr->addrlen = sizeof(addr->in6); } else if (*str == '*') { /* IPv4, *:p */ debug(DBG_CFG, "attempting to parse IPv4 addr (%s)\n", str); str++; if (*str != ':') goto out; str++; if (strtou16_strict(str, &port) < 0) goto out; addr = zmalloc(sizeof(*addr)); if (!addr) goto out; list_add(&addr->list, list); addr->in4.sin_family = AF_INET; addr->in4.sin_addr.s_addr = INADDR_ANY; addr->in4.sin_port = htons(port); addr->addrlen = sizeof(addr->in4); } 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 out; addr = zmalloc(sizeof(*addr)); if (!addr) goto out; if (inet_pton(AF_INET, str, &addr->in4.sin_addr) > 0) { debug(DBG_CFG, "got an IPv4:port (%s)\n", str); addr->in4.sin_family = AF_INET; addr->in4.sin_port = htons(port); addr->addrlen = sizeof(addr->in4); list_add(&addr->list, list); goto success; } else { xfree(addr); } 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 out; } debug(DBG_CFG, "got a hostname:port (%s)\n", str); for (ai = results; ai; ai = ai->ai_next) { addr = zmalloc(sizeof(*addr)); if (!addr) { freeaddrinfo(results); goto out; } switch (ai->ai_family) { case AF_INET: { struct sockaddr_in *naddr = (struct sockaddr_in *)ai->ai_addr; addr->in4.sin_family = AF_INET; addr->in4.sin_addr = naddr->sin_addr; addr->in4.sin_port = naddr->sin_port; addr->addrlen = sizeof(addr->in4); list_add(&addr->list, list); break; } case AF_INET6: { struct sockaddr_in6 *naddr = (struct sockaddr_in6 *)ai->ai_addr; addr->in6.sin6_family = AF_INET6; addr->in6.sin6_addr = naddr->sin6_addr; addr->in6.sin6_port = naddr->sin6_port; addr->addrlen = sizeof(addr->in6); list_add(&addr->list, list); break; } default: error("getaddrinfo(%s): unknown address family (%i)\n", str, ai->ai_family); xfree(addr); continue; } } freeaddrinfo(results); } else if (strtou16_strict(tmp, &port) == 0) { /* Port */ debug(DBG_CFG, "attempting to parse a port number (%s)\n", str); addr = zmalloc(sizeof(*addr)); if (!addr) goto out; addr->in6.sin6_family = AF_INET6; addr->in6.sin6_addr = in6addr_any; addr->in6.sin6_port = htons(port); addr->addrlen = sizeof(addr->in6); list_add(&addr->list, list); addr = zmalloc(sizeof(*addr)); if (!addr) goto out; addr->in4.sin_family = AF_INET; addr->in4.sin_addr.s_addr = INADDR_ANY; addr->in4.sin_port = htons(port); addr->addrlen = sizeof(addr->in4); list_add(&addr->list, list); } else { /* Unknown */ error("unable to parse address: %s\n", str); goto out; } success: if (list_empty(list)) { error("empty address list!?\n"); return false; } else { int i = 0; list_for_each_entry(addr, list, list) i++; debug(DBG_CFG, "parsed to %i addresses\n", i); } return true; out: if (!list_empty(list)) { struct sockaddr_in46 *tmp; list_for_each_entry_safe(addr, tmp, list, list) { list_del(&addr->list); xfree(addr); } } return false; } /* Returns true if theres 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 out; key = tmp; while (*tmp != '\0' && !isspace(*tmp)) tmp++; if (*tmp == '\0') goto out; *tmp = '\0'; tmp++; while (isspace(*tmp)) tmp++; if (*tmp != '=') goto out; tmp++; while (isspace(*tmp)) tmp++; if (*tmp == '\0') goto out; 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 out; rvalue->uint16 = v; break; } case CFG_VAL_TYPE_ADDRS: { if (!strtosockaddrs(tmp, &rvalue->addrs)) goto out; if (list_empty(&rvalue->addrs)) { error("empty address list\n"); goto out; } break; } case CFG_VAL_TYPE_INVALID: /* fall through */ default: goto out; } *rkey = kvmap[i].key_value; *rkeyname = kvmap[i].key_name; return true; } out: /* 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)) { printf("%s: incorrect header in configuration file\n", filename); return false; } } return true; }