#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); }