From db66484c4300f5f0e857eff01d15fd3593002a79 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Fri, 5 Jun 2020 14:09:18 +0200 Subject: Initial commit --- config.c | 730 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 730 insertions(+) create mode 100644 config.c (limited to 'config.c') diff --git a/config.c b/config.c new file mode 100644 index 0000000..f362920 --- /dev/null +++ b/config.c @@ -0,0 +1,730 @@ +#include +#include +#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 */ + 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 */ + 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 */ + fprintf(stderr, "Got an IPv4:port or hostname:port\n"); + *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) { + fprintf(stderr, "...Got an IPv4:port (0x%p, list 0x%p)\n", addr, &addr->list); + 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 { + free(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) { + fprintf(stderr, "gettaddrinfo(%s): %s\n", str, gai_strerror(r)); + goto out; + } + + fprintf(stderr, "...Got an hostname:port\n"); + for (ai = results; ai; ai = ai->ai_next) { + fprintf(stderr, "Got a result from getaddrinfo\n"); + + 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: + fprintf(stderr, " Fam: Unknown (%i)\n", ai->ai_family); + free(addr); + continue; + } + } + + freeaddrinfo(results); + + } else if (strtou16_strict(tmp, &port) == 0) { + /* Port */ + + 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); + } + +success: + if (list_empty(list)) { + fprintf(stderr, "Success but empty list!?\n"); + return false; + } else { + int i = 0; + struct list_head *pos; + + list_for_each(pos, list) + i++; + fprintf(stderr, "Success, %i entries\n", i); + } + + return true; + +out: + if (!list_empty(list)) { + struct sockaddr_in46 *tmpaddr; + + list_for_each_entry_safe(addr, tmpaddr, list, list) { + list_del(&addr->list); + free(addr); + } + } + return false; +} + +enum scfg_keys { + SCFG_KEY_TYPE, + SCFG_KEY_NAME, + SCFG_KEY_PORT, + SCFG_KEY_LOCAL, + SCFG_KEY_REMOTE, + SCFG_KEY_INVALID +}; + +enum value_type { + VAL_TYPE_STRING, + VAL_TYPE_UINT16, + VAL_TYPE_ADDR, + VAL_TYPE_INVALID +}; + +struct scfg_key_map { + const char *key_name; + enum scfg_keys key_value; + enum value_type value_type; +} scfg_key_map[] = { + { + .key_name = "type", + .key_value = SCFG_KEY_TYPE, + .value_type = VAL_TYPE_STRING, + }, { + .key_name = "name", + .key_value = SCFG_KEY_NAME, + .value_type = VAL_TYPE_STRING, + }, { + .key_name = "port", + .key_value = SCFG_KEY_PORT, + .value_type = VAL_TYPE_UINT16, + }, { + .key_name = "local", + .key_value = SCFG_KEY_LOCAL, + .value_type = VAL_TYPE_ADDR, + }, { + .key_name = "remote", + .key_value = SCFG_KEY_REMOTE, + .value_type = VAL_TYPE_ADDR, + } +}; + +union cfg_value { + const char *str; + uint16_t uint16; + struct list_head addr_list; +}; + + +static void +line_get_key_value(char *line, enum scfg_keys *rkey, union cfg_value *rvalue) +{ + char *tmp, *key; + int i; + + *rkey = SCFG_KEY_INVALID; + if (!line) + return; + + tmp = line; + while (isspace(*tmp)) + tmp++; + + if (*tmp == '\0') + return; + + key = tmp; + while (*tmp != '\0' && !isspace(*tmp)) + tmp++; + + if (*tmp == '\0') + return; + + *tmp = '\0'; + tmp++; + + while (isspace(*tmp)) + tmp++; + + if (*tmp != '=') + return; + + tmp++; + while (isspace(*tmp)) + tmp++; + + if (*tmp == '\0') + return; + + for (i = 0; i < ARRAY_SIZE(scfg_key_map); i++) { + if (strcmp(scfg_key_map[i].key_name, key)) + continue; + + switch (scfg_key_map[i].value_type) { + + case VAL_TYPE_STRING: + rvalue->str = tmp; + break; + + case VAL_TYPE_UINT16: { + uint16_t v; + + if (strtou16_strict(tmp, &v) < 0) + return; + rvalue->uint16 = v; + break; + } + + case VAL_TYPE_ADDR: { + if (!strtosockaddrs(tmp, &rvalue->addr_list)) + return; + if (list_empty(&rvalue->addr_list)) { + fprintf(stderr, "VAL_TYPE_ADDR with zero list!?\n"); + return; + } else { + int i = 0; + struct list_head *pos; + + list_for_each(pos, &rvalue->addr_list) + i++; + fprintf(stderr, "VAL_TYPE_ADDR with list %i entries\n", i); + } + break; + } + + case VAL_TYPE_INVALID: + /* fall through */ + default: + return; + } + *rkey = scfg_key_map[i].key_value; + break; + } +} + +static void +scfg_parse(struct cfg *cfg, struct server *scfg) +{ + char *pos = &scfg->buf[0]; + char *line; + + eat_whitespace_and_comments(&pos); + + line = get_line(&pos); + if (!line) { + printf("Cfg: premature EOF\n"); + return; + } + + if (strcmp(line, "[server]")) { + printf("Invalid line: %s\n", line); + return; + } + + while (true) { + enum scfg_keys key; + union cfg_value value; + + eat_whitespace_and_comments(&pos); + line = get_line(&pos); + printf("Examining line: %s\n", line); + line_get_key_value(line, &key, &value); + if (key == SCFG_KEY_INVALID) + break; + printf("Got a key-value pair: %i = something\n", key); + + switch (key) { + + case SCFG_KEY_TYPE: + if (!strcmp(value.str, "proxy")) { + if (!server_set_type(cfg, scfg, SERVER_TYPE_PROXY)) + return; + } else if (!strcmp(value.str, "announce")) { + if (!server_set_type(cfg, scfg, SERVER_TYPE_ANNOUNCE)) + return; + } + break; + + case SCFG_KEY_NAME: + if (!server_set_pretty_name(cfg, scfg, value.str)) + return; + break; + + case SCFG_KEY_PORT: + if (!server_set_port(cfg, scfg, value.uint16)) + return; + break; + + case SCFG_KEY_LOCAL: { + struct sockaddr_in46 *addr, *tmp; + + list_for_each_entry_safe(addr, tmp, &value.addr_list, list) { + list_del(&addr->list); + server_add_local(cfg, scfg, addr); + } + break; + } + + case SCFG_KEY_REMOTE: { + struct sockaddr_in46 *addr, *tmp; + + list_for_each_entry_safe(addr, tmp, &value.addr_list, list) { + list_del(&addr->list); + server_add_remote(cfg, scfg, addr); + } + break; + } + + case SCFG_KEY_INVALID: + default: + break; + } + } + + //printf("Cfg:\n%s\n\n", pos); +} + +static void +scfg_read_cb(struct cfg *cfg, struct uring_task *task, int res) +{ + struct server *scfg = container_of(task, struct server, task); + + printf("Asked to parse server cfg %s (bytes %i)\n", scfg->name, res); + + if (res < 0) { + perrordie("read"); + } else if (res > 0) { + scfg->len += res; + if (scfg->len + 1 >= sizeof(scfg->buf)) { + fprintf(stderr, "Server config too large\n"); + server_delete(cfg, scfg); + return; + } + + uring_read(cfg, &scfg->task, scfg->buf + scfg->len, sizeof(scfg->buf) - scfg->len, scfg->len, scfg_read_cb); + return; + } else { + /* EOF */ + scfg->buf[scfg->len] = '\0'; + uring_task_close_fd(cfg, &scfg->task); + scfg_parse(cfg, scfg); + server_commit(cfg, scfg); + } +} + +static void +scfg_open_cb(struct cfg *cfg, struct uring_task *task, int res) +{ + struct server *scfg = container_of(task, struct server, task); + + if (res < 0) { + fprintf(stderr, "Open failed\n"); + server_delete(cfg, scfg); + return; + } + + printf("Asked to read server cfg %s (fd %i)\n", scfg->name, res); + uring_task_set_fd(&scfg->task, res); + scfg->len = 0; + uring_read(cfg, &scfg->task, scfg->buf, sizeof(scfg->buf), 0, scfg_read_cb); +} + +static bool +scfg_valid_filename(const char *name) +{ + const char *suffix; + + if (!name) + return false; + if (name[0] == '\0') + return false; + if (name[0] == '.') + return false; + if ((suffix = strrchr(name, '.')) == NULL) + return false; + if (strcmp(suffix, ".server")) + return false; + + return true; +} + +struct inotify_ev { + struct uring_task task; + char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); +}; + +static void +inotify_free(struct uring_task *task) +{ + struct inotify_ev *iev = container_of(task, struct inotify_ev, task); + struct cfg *cfg = container_of(task->parent, struct cfg, task); + + fprintf(stderr, "%s called\n", __func__); + if (!iev || !cfg) + die("%s: iev or cfg is NULL!?\n", __func__); + + free(iev); + cfg->iev = NULL; + uring_task_put(cfg, &cfg->task); +} + +static void +inotify_event_dump(const struct inotify_event *event) +{ + printf("Event:\n"); + printf(" * WD : %i\n", event->wd); + printf(" * Cookie : %" PRIu32 "\n", event->cookie); + printf(" * Length : %" PRIu32 "\n", event->len); + printf(" * Name : %s\n", event->name); + printf(" * Mask : %" PRIu32 "\n", event->mask); + if (event->mask & IN_ACCESS) + printf("\tIN_ACCESS\n"); + else if(event->mask & IN_MODIFY) + printf("\tIN_MODIFY\n"); + else if(event->mask & IN_ATTRIB) + printf("\tIN_ATTRIB\n"); + else if(event->mask & IN_CLOSE_WRITE) + printf("\tIN_CLOSE_WRITE\n"); + else if(event->mask & IN_CLOSE_NOWRITE) + printf("\tIN_CLOSE_NOWRITE\n"); + else if(event->mask & IN_OPEN) + printf("\tIN_OPEN\n"); + else if(event->mask & IN_MOVED_FROM) + printf("\tIN_MOVED_FROM\n"); + else if(event->mask & IN_MOVED_TO) + printf("\tIN_MOVED_TO\n"); + else if(event->mask & IN_CREATE) + printf("\tIN_CREATE\n"); + else if(event->mask & IN_DELETE) + printf("\tIN_DELETE\n"); + else if(event->mask & IN_DELETE_SELF) + printf("\tIN_DELETE_SELF\n"); + else if(event->mask & IN_MOVE_SELF) + printf("\tIN_MOVE_SELF\n"); + else if(event->mask & IN_UNMOUNT) + printf("\tIN_UNMOUNT\n"); + else if(event->mask & IN_Q_OVERFLOW) + printf("\tIN_Q_OVERFLOW\n"); + else if(event->mask & IN_IGNORED) + printf("\tIN_IGNORED\n"); + printf("\n"); +} + +static void +inotify_cb(struct cfg *cfg, struct uring_task *task, int res) +{ + struct inotify_ev *iev = container_of(task, struct inotify_ev, task); + const struct inotify_event *event; + char *ptr; + struct server *scfg; + + fprintf(stderr, "%s: ret is %i (ref %u)\n", __func__, res, task->refcount); + + if (task->dead) { + fprintf(stderr, "%s: task is dead\n", __func__); + uring_task_put(cfg, task); + return; + } + + if (res <= 0) + perrordie("inotify_read"); + + for (ptr = iev->buf; ptr < iev->buf + res; ptr += sizeof(struct inotify_event) + event->len) { + event = (const struct inotify_event *)ptr; + + if (debuglvl > 0) + inotify_event_dump(event); + + if (event->mask & (IN_IGNORED | IN_MOVE_SELF | IN_DELETE_SELF | IN_UNMOUNT)) + die("Configuration directory gone, exiting\n"); + + if (event->mask & IN_Q_OVERFLOW) { + error("inotify queue overflow!\n"); + continue; + } + + if (!scfg_valid_filename(event->name)) + continue; + + if (event->mask & (IN_MOVED_FROM | IN_DELETE)) + server_delete_by_name(cfg, event->name); + else if (event->mask & (IN_MOVED_TO | IN_CREATE | IN_CLOSE_WRITE)) { + scfg = server_new(cfg, event->name); + uring_openat(cfg, &scfg->task, event->name, scfg_open_cb); + } else + error("inotify: weird, unknown event: 0x%08x\n", event->mask); + } + + uring_read(cfg, &iev->task, iev->buf, sizeof(iev->buf), 0, inotify_cb); +} + +void +inotify_refdump(struct inotify_ev *iev) +{ + uring_task_refdump(&iev->task); +} + +void +scfg_stop_monitor_dir(struct cfg *cfg) +{ + if (!cfg->iev) { + fprintf(stderr, "%s called with no iev!\n", __func__); + return; + } + + fprintf(stderr, "%s called, closing fd %i\n", __func__, cfg->iev->task.fd); + uring_cancel(cfg, &cfg->iev->task); + cfg->iev = NULL; +} + +void +scfg_monitor_dir(struct cfg *cfg) +{ + int ifd; + int iwd; + struct inotify_ev *iev; + + iev = malloc(sizeof(*iev)); + if (!iev) + perrordie("malloc"); + + ifd = inotify_init1(IN_CLOEXEC); + if (ifd < 0) + perrordie("inotify_init1"); + + /* ln = IN_CREATE, cp/vi/mv = IN_CREATE, IN_OPEN, IN_CLOSE_WRITE */ + iwd = inotify_add_watch(ifd, ".", + IN_CLOSE_WRITE | IN_DELETE | IN_CREATE | + IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO | + IN_MOVED_FROM | IN_DONT_FOLLOW | + IN_EXCL_UNLINK | IN_ONLYDIR ); + if (iwd < 0) + perrordie("inotify_add_watch"); + + uring_task_init(&iev->task, "iev", &cfg->task, inotify_free); + uring_task_set_fd(&iev->task, ifd); + cfg->iev = iev; + uring_read(cfg, &iev->task, iev->buf, sizeof(iev->buf), 0, inotify_cb); +} + +void +scfg_read_all(struct cfg *cfg) +{ + + DIR *cfgdir; + struct dirent *dent; + struct server *scfg; + + cfgdir = opendir("."); + if (!cfgdir) { + perror("opendir"); + free(cfg); + return; + } + + while ((dent = readdir(cfgdir)) != NULL) { + char *suffix; + + if (dent->d_name[0] == '.') + continue; + if (dent->d_type != DT_REG && dent->d_type != DT_UNKNOWN) + continue; + if ((suffix = strrchr(dent->d_name, '.')) == NULL) + continue; + if (strcmp(suffix, ".server")) + continue; + + scfg = server_new(cfg, dent->d_name); + uring_openat(cfg, &scfg->task, dent->d_name, scfg_open_cb); + } + + closedir(cfgdir); +} + -- cgit v1.2.3