From 809c4e0b2f4ced53f48b092f43b37c08eae18b75 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Sun, 12 Jul 2020 22:34:18 +0200 Subject: Move main.c to minecproxy.c for consistency and better debug msgs --- minecproxy/announce.c | 2 +- minecproxy/idle.c | 2 +- minecproxy/igmp.c | 2 +- minecproxy/main.c | 693 -------------------------------------------- minecproxy/main.h | 88 ------ minecproxy/meson.build | 2 +- minecproxy/minecproxy.c | 693 ++++++++++++++++++++++++++++++++++++++++++++ minecproxy/minecproxy.h | 88 ++++++ minecproxy/misc.c | 2 +- minecproxy/ptimer.c | 2 +- minecproxy/server-config.c | 2 +- minecproxy/server-proxy.c | 2 +- minecproxy/server-rcon.c | 2 +- minecproxy/server.c | 2 +- minecproxy/signal-handler.c | 2 +- minecproxy/uring.c | 2 +- 16 files changed, 793 insertions(+), 793 deletions(-) delete mode 100644 minecproxy/main.c delete mode 100644 minecproxy/main.h create mode 100644 minecproxy/minecproxy.c create mode 100644 minecproxy/minecproxy.h diff --git a/minecproxy/announce.c b/minecproxy/announce.c index 4193a75..3e4a31e 100644 --- a/minecproxy/announce.c +++ b/minecproxy/announce.c @@ -6,7 +6,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "announce.h" #include "server.h" diff --git a/minecproxy/idle.c b/minecproxy/idle.c index b4f804f..8393ff9 100644 --- a/minecproxy/idle.c +++ b/minecproxy/idle.c @@ -7,7 +7,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "server.h" #include "idle.h" diff --git a/minecproxy/igmp.c b/minecproxy/igmp.c index 591ac4f..bf46844 100644 --- a/minecproxy/igmp.c +++ b/minecproxy/igmp.c @@ -13,7 +13,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "igmp.h" #include "announce.h" diff --git a/minecproxy/main.c b/minecproxy/main.c deleted file mode 100644 index 92065cc..0000000 --- a/minecproxy/main.c +++ /dev/null @@ -1,693 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "signal-handler.h" -#include "uring.h" -#include "shared/config-parser.h" -#include "server.h" -#include "server-config.h" -#include "announce.h" -#include "shared/systemd.h" -#include "igmp.h" -#include "idle.h" -#include "ptimer.h" -#include "config.h" -#include "minecproxy-config-options.h" - -/* Global */ -struct cfg *cfg = NULL; -bool exiting = false; - -/* Local */ -static bool daemonize = false; -static FILE *log_file = NULL; -static const char *log_file_path = NULL; - -static void set_logging_type(bool *use_colors, bool *sd_daemon) -{ - int fd; - const char *e; - - /* assume we're not launched by systemd when daemonized */ - if (daemonize) { - *sd_daemon = false; - *use_colors = false; - return; - } - - if (log_file) { - *sd_daemon = false; - *use_colors = false; - return; - } - - if (getenv("NO_COLOR")) { - *sd_daemon = false; - *use_colors = false; - return; - } - - fd = fileno(stderr); - if (fd < 0) { - /* Umm... */ - *sd_daemon = true; - *use_colors = false; - return; - } - - if (!isatty(fd)) { - *sd_daemon = true; - *use_colors = false; - return; - } - - /* systemd wouldn't normally set TERM */ - e = getenv("TERM"); - if (!e) { - *sd_daemon = true; - *use_colors = false; - return; - } - - if (streq(e, "dumb")) { - *sd_daemon = false; - *use_colors = false; - return; - } - - *sd_daemon = false; - *use_colors = true; -} - -static void msg(enum debug_lvl lvl, const char *fmt, va_list ap) -{ - static bool first = true; - static bool sd_daemon; - const char *color; - const char *sd_lvl; - - assert_return(lvl != 0 && !empty_str(fmt) && ap); - - if (first) { - bool use_colors; - - set_logging_type(&use_colors, &sd_daemon); - if (use_colors) - enable_colors(); - - first = false; - } - - switch (lvl) { - case DBG_ERROR: - sd_lvl = SD_ERR; - color = ansi_red; - break; - case DBG_VERBOSE: - sd_lvl = SD_INFO; - color = NULL; - break; - case DBG_INFO: - sd_lvl = SD_NOTICE; - color = NULL; - break; - default: - sd_lvl = SD_DEBUG; - color = ansi_grey; - break; - } - - if (sd_daemon) - fprintf(stderr, "%s", sd_lvl); - else if (color) - fprintf(stderr, "%s", color); - - vfprintf(log_file ? log_file : stderr, fmt, ap); - - if (color) - fprintf(stderr, "%s", ansi_normal); -} - -void __debug(enum debug_lvl lvl, const char *fmt, ...) -{ - va_list ap; - - assert_return(lvl != 0 && !empty_str(fmt)); - - va_start(ap, fmt); - msg(lvl, fmt, ap); - va_end(ap); -} - -_noreturn_ void __die(const char *fmt, ...) -{ - va_list ap; - - if (!empty_str(fmt)) { - va_start(ap, fmt); - msg(DBG_ERROR, fmt, ap); - va_end(ap); - } else - error("fmt not set"); - - sd_notifyf(0, "STATUS=Error, shutting down"); - exit(EXIT_FAILURE); -} - -static void cfg_free(struct uring_task *task) -{ - struct cfg *xcfg = container_of(task, struct cfg, task); - - assert_return(task && xcfg == cfg); - - debug(DBG_SIG, "called"); - systemd_delete(); - xfree(cfg->igmp_iface); - cfg->igmp_iface = NULL; - xfree(cfg->data_real_path); - cfg->data_real_path = NULL; - xfree(cfg->cfg_real_path); - cfg->cfg_real_path = NULL; - if (cfg->data_dir) { - closedir(cfg->data_dir); - cfg->data_dir = NULL; - } - if (cfg->cfg_dir) { - closedir(cfg->cfg_dir); - cfg->cfg_dir = NULL; - } - exiting = true; - /* The cfg struct is free:d in main() */ -} - -static void cfg_read() -{ - char buf[4096]; - char *pos; - size_t off; - size_t r; - unsigned lineno; - _cleanup_close_ int fd = -1; - _cleanup_fclose_ FILE *cfgfile = NULL; - - assert_return(cfg); - - fd = openat(dirfd(cfg->cfg_dir), MINECPROXY_CFG_FILE, - O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (fd < 0) - return; - - cfgfile = fdopen(fd, "re"); - if (!cfgfile) - return; - - fd = -1; - debug(DBG_CFG, "opened main config file %s/%s", cfg->cfg_real_path, - MINECPROXY_CFG_FILE); - - for (off = 0; off < sizeof(buf); off += r) { - r = fread(buf + off, 1, sizeof(buf) - off, cfgfile); - if (r == 0) - break; - } - - if (off >= sizeof(buf) - 1) - die("main config file %s/%s too large", cfg->cfg_real_path, - MINECPROXY_CFG_FILE); - - buf[off] = '\0'; - pos = buf; - - if (!config_parse_header(MINECPROXY_CFG_HEADER, &pos, &lineno)) - die("main config file %s/%s missing/invalid header", - cfg->cfg_real_path, MINECPROXY_CFG_FILE); - - while (true) { - int key; - const char *keyname; - struct cfg_value value; - const char *error; - - if (!config_parse_line(MINECPROXY_CFG_FILE, &pos, - mcfg_key_map, &key, &keyname, - &value, false, &lineno, &error)) - break; - - if (key == MCFG_KEY_INVALID) - die("main config file %s/%s invalid: line %u: %s", - cfg->cfg_real_path, MINECPROXY_CFG_FILE, lineno, - error); - - debug(DBG_CFG, "main cfg: key %s", keyname); - - switch (key) { - case MCFG_KEY_IGMP: - cfg->do_igmp = value.boolean; - break; - - case MCFG_KEY_IGMP_IFACE: - cfg->igmp_iface = xstrdup(value.str); - if (!cfg->igmp_iface) - die("xstrdup: %m"); - - break; - - case MCFG_KEY_ANNOUNCE_INTERVAL: - cfg->announce_interval = value.uint16; - break; - - case MCFG_KEY_PROXY_CONN_INTERVAL: - cfg->proxy_connection_interval = value.uint16; - break; - - case MCFG_KEY_PROXY_CONN_ATTEMPTS: - cfg->proxy_connection_attempts = value.uint16; - break; - - case MCFG_KEY_SOCKET_DEFER: - cfg->socket_defer = value.boolean; - break; - - case MCFG_KEY_SOCKET_FREEBIND: - cfg->socket_freebind = value.boolean; - break; - - case MCFG_KEY_SOCKET_KEEPALIVE: - cfg->socket_keepalive = value.boolean; - break; - - case MCFG_KEY_SOCKET_IPTOS: - cfg->socket_iptos = value.boolean; - break; - - case MCFG_KEY_SOCKET_NODELAY: - cfg->socket_nodelay = value.boolean; - break; - - case MCFG_KEY_INVALID: - default: - die("main config file invalid"); - } - } -} - -/* clang-format off */ -const struct { - const char *name; - unsigned val; -} debug_category_str[] = { - { - .name = "config", - .val = DBG_CFG, - },{ - .name = "refcount", - .val = DBG_REF, - },{ - .name = "malloc", - .val = DBG_MALLOC, - },{ - .name = "announce", - .val = DBG_ANN, - },{ - .name = "signal", - .val = DBG_SIG, - },{ - .name = "uring", - .val = DBG_UR, - },{ - .name = "server", - .val = DBG_SRV, - },{ - .name = "proxy", - .val = DBG_PROXY, - },{ - .name = "rcon", - .val = DBG_RCON, - },{ - .name = "idle", - .val = DBG_IDLE, - },{ - .name = "igmp", - .val = DBG_IGMP, - },{ - .name = "systemd", - .val = DBG_SYSD, - },{ - .name = "dns", - .val = DBG_DNS, - },{ - .name = "timer", - .val = DBG_TIMER, - },{ - .name = NULL, - .val = 0, - } -}; -/* clang-format on */ - -_noreturn_ static void usage(bool invalid) -{ - if (invalid) - info("Invalid option(s)"); - - info("Usage: %s [OPTIONS]\n" - "\n" - "Valid options:\n" - " -c, --cfgdir=DIR\tuse DIR for configuration files\n" - " -C, --datadir=DIR\tuse DIR for server data\n" - " -u, --user=USER\trun as USER\n" - " -D, --daemonize\trun in daemon mode (disables stderr output)\n" - " -l, --logfile=FILE\tlog to FILE instead of stderr\n" - " -h, --help\t\tprint this information\n" - " -v, --verbose\t\tenable verbose logging\n" - " -d, --debug=CATEGORY\tenable debugging for CATEGORY\n" - "\t\t\t(use \"list\" to see available categories,\n" - "\t\t\t or \"all\" to enable all categories)\n", - program_invocation_short_name); - - exit(invalid ? EXIT_FAILURE : EXIT_SUCCESS); -} - -static void cfg_init(int argc, char **argv) -{ - int c; - unsigned i; - - assert_die(argc > 0 && argv, "invalid arguments"); - - cfg = zmalloc(sizeof(*cfg)); - if (!cfg) - die("malloc: %m"); - - uring_task_init(&cfg->task, "main", NULL, cfg_free); - INIT_LIST_HEAD(&cfg->servers); - - cfg->cfg_path = NULL; - cfg->cfg_real_path = NULL; - cfg->cfg_dir = NULL; - cfg->data_path = NULL; - cfg->data_real_path = NULL; - cfg->data_dir = NULL; - cfg->announce_interval = DEFAULT_ANNOUNCE_INTERVAL; - cfg->proxy_connection_interval = DEFAULT_PROXY_CONN_INTERVAL; - cfg->proxy_connection_attempts = DEFAULT_PROXY_CONN_ATTEMPTS; - cfg->socket_defer = DEFAULT_SOCKET_DEFER; - cfg->socket_freebind = DEFAULT_SOCKET_FREEBIND; - cfg->socket_keepalive = DEFAULT_SOCKET_KEEPALIVE; - cfg->socket_iptos = DEFAULT_SOCKET_IPTOS; - cfg->socket_nodelay = DEFAULT_SOCKET_NODELAY; - cfg->uid = geteuid(); - cfg->gid = getegid(); - - while (true) { - int option_index = 0; - /* clang-format off */ - static struct option long_options[] = { - { "cfgdir", required_argument, 0, 'c' }, - { "datadir", required_argument, 0, 'C' }, - { "user", required_argument, 0, 'u' }, - { "daemonize", no_argument, 0, 'D' }, - { "logfile", required_argument, 0, 'l' }, - { "help", no_argument, 0, 'h' }, - { "verbose", no_argument, 0, 'v' }, - { "debug", required_argument, 0, 'd' }, - { 0, 0, 0, 0 } - }; - /* clang-format on */ - - c = getopt_long(argc, argv, ":c:C:u:Dl:hvd:", long_options, - &option_index); - if (c == -1) - break; - - switch (c) { - case 'c': - cfg->cfg_path = optarg; - break; - - case 'C': - cfg->data_path = optarg; - break; - - case 'v': - debug_mask |= DBG_VERBOSE; - break; - - case 'D': - daemonize = true; - break; - - case 'l': - log_file_path = optarg; - break; - - case 'u': { - struct passwd *pwd; - - errno = 0; - pwd = getpwnam(optarg); - if (!pwd) { - if (errno == 0) - errno = ESRCH; - if (errno == ESRCH) - die("failed to find user %s", optarg); - else - die("failed to find user %s (%m)", - optarg); - } - - debug(DBG_CFG, "asked to execute with uid %ji gid %ji", - (intmax_t)pwd->pw_uid, (intmax_t)pwd->pw_gid); - cfg->uid = pwd->pw_uid; - cfg->gid = pwd->pw_gid; - break; - } - - case 'd': - if (strcaseeq(optarg, "all")) { - debug_mask = ~0; - break; - } else if (strcaseeq(optarg, "list")) { - info("Debug categories:"); - info(" * all"); - for (i = 0; debug_category_str[i].name; i++) - info(" * %s", - debug_category_str[i].name); - exit(EXIT_FAILURE); - } - - for (i = 0; debug_category_str[i].name; i++) { - if (strcaseeq(optarg, - debug_category_str[i].name)) - break; - } - - if (!debug_category_str[i].name) - usage(true); - - debug_mask |= DBG_VERBOSE; - debug_mask |= debug_category_str[i].val; - break; - - case 'h': - usage(false); - - default: - usage(true); - } - } - - if (optind < argc) - usage(true); -} - -static void cfg_apply() -{ - if (cfg->uid == 0 || cfg->gid == 0) - /* This catches both -u root and running as root without -u */ - die("Execution as root is not supported (use -u )"); - - capng_clear(CAPNG_SELECT_BOTH); - if (capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, - CAP_NET_RAW, CAP_NET_BIND_SERVICE, -1)) - die("capng_updatev failed"); - - if (geteuid() != cfg->uid) { - if (capng_change_id(cfg->uid, cfg->gid, - CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) - die("capng_change_id failed"); - } else { - /* - * This can fail if any of the caps are lacking, but it'll - * be re-checked later. - */ - capng_apply(CAPNG_SELECT_BOTH); - setgroups(0, NULL); - } - - if (daemonize) { - if (daemon(1, 0) < 0) - die("daemon() failed: %m"); - } - - if (log_file_path) { - log_file = fopen(log_file_path, "ae"); - if (!log_file) - die("fopen(%s) failed: %m", log_file_path); - } - - /* - * Do this after caps have been dropped to make sure we're not - * accessing a directory we should have permissions to. - */ - cfg->cfg_dir = open_cfg_dir(cfg->cfg_path, &cfg->cfg_real_path); - if (!cfg->cfg_dir) - die("Unable to open configuration directory"); - - cfg->data_dir = open_data_dir(cfg->data_path, &cfg->data_real_path); - if (!cfg->data_dir) - die("Unable to open server directory"); - - if (fchdir(dirfd(cfg->data_dir))) - die("Unable to chdir to server directory: %m"); - - if (debug_enabled(DBG_VERBOSE)) { - char *wd; - - wd = get_current_dir_name(); - verbose("Working directory: %s", wd ? wd : ""); - free(wd); - } -} - -void dump_tree() -{ - struct server *server; - - if (!debug_enabled(DBG_REF)) - return; - - info("\n\n"); - info("Dumping Tree"); - info("============"); - uring_task_refdump(&cfg->task); - uring_refdump(); - signal_refdump(); - ptimer_refdump(); - idle_refdump(); - igmp_refdump(); - announce_refdump(); - server_cfg_monitor_refdump(); - list_for_each_entry(server, &cfg->servers, list) - server_refdump(server); - info("============"); - info("\n\n"); -} - -int main(int argc, char **argv) -{ - struct server *server; - unsigned server_count; - struct rlimit old_rlimit; - - debug_mask = DBG_ERROR | DBG_INFO; - - cfg_init(argc, argv); - - cfg_apply(); - - cfg_read(); - - /* - * In the splice case we use 6 fds per proxy connection... - */ - if (prlimit(0, RLIMIT_NOFILE, NULL, &old_rlimit) == 0) { - struct rlimit new_rlimit; - - new_rlimit.rlim_cur = old_rlimit.rlim_max; - new_rlimit.rlim_max = old_rlimit.rlim_max; - - if (prlimit(0, RLIMIT_NOFILE, &new_rlimit, NULL) == 0) - debug(DBG_MALLOC, "prlimit(NOFILE): %u/%u -> %u/%u", - (unsigned)old_rlimit.rlim_cur, - (unsigned)old_rlimit.rlim_max, - (unsigned)new_rlimit.rlim_cur, - (unsigned)new_rlimit.rlim_cur); - } - - uring_init(); - - ptimer_init(); - - igmp_init(); - - /* Drop CAP_NET_RAW (if we have it), only used for igmp */ - capng_clear(CAPNG_SELECT_BOTH); - if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, - CAP_NET_BIND_SERVICE)) - die("capng_update failed"); - - if (capng_apply(CAPNG_SELECT_BOTH)) { - /* Try clearing all caps, shouldn't fail */ - capng_clear(CAPNG_SELECT_BOTH); - if (capng_apply(CAPNG_SELECT_BOTH)) - die("capng_apply failed"); - } - - signal_init(); - - server_cfg_monitor_init(); - - announce_init(); - - if (!cfg->igmp) - announce_start(0); - - idle_init(); - - uring_task_put(&cfg->task); - - server_count = 0; - list_for_each_entry(server, &cfg->servers, list) - server_count++; - - sd_notifyf(0, - "READY=1\n" - "STATUS=Running, %u server configurations loaded\n" - "MAINPID=%lu", - server_count, (unsigned long)getpid()); - - info("mcproxy (%s) started, %u server configurations loaded", VERSION, - server_count); - - uring_event_loop(); - - verbose("Exiting"); - - xfree(cfg); - cfg = NULL; - - if (debug_enabled(DBG_MALLOC)) - debug_resource_usage(); - - fflush(stdout); - fflush(stderr); - exit(EXIT_SUCCESS); -} diff --git a/minecproxy/main.h b/minecproxy/main.h deleted file mode 100644 index f7dd547..0000000 --- a/minecproxy/main.h +++ /dev/null @@ -1,88 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef foomainhfoo -#define foomainhfoo - -#include -#include - -struct cfg; -struct uring_task; - -#include "misc.h" -#include "shared/utils.h" - -extern struct cfg *cfg; -extern bool exiting; - -void dump_tree(); - -/* To save typing in all the function definitions below */ -typedef void (*utask_cb_t)(struct uring_task *, int res); -typedef int (*rutask_cb_t)(struct uring_task *, int res); - -struct uring_task_buf { - char buf[4096]; - size_t len; - size_t done; - struct iovec iov; - struct msghdr msg; -}; - -struct uring_task { - const char *name; - int refcount; /* signed to catch refcount bugs */ - int fd; - struct uring_task *parent; - void (*free)(struct uring_task *); - bool dead; - struct uring_task_buf *tbuf; - - /* called once or repeatedly until is_complete_cb is satisfied */ - utask_cb_t cb; - - /* returns: 0 = not complete; < 0 = error; > 0 = complete */ - rutask_cb_t is_complete_cb; - - /* called once tbuf processing is done */ - utask_cb_t final_cb; - - /* used for recvmsg/sendmsg */ - struct saddr saddr; - void *priv; -}; - -struct cfg { - /* Options */ - const char *cfg_path; - char *cfg_real_path; - DIR *cfg_dir; - const char *data_path; - char *data_real_path; - DIR *data_dir; - uid_t uid; - gid_t gid; - bool do_igmp; - char *igmp_iface; - bool splice_supported; - uint16_t announce_interval; - uint16_t proxy_connection_interval; - uint16_t proxy_connection_attempts; - bool socket_defer; - bool socket_freebind; - bool socket_keepalive; - bool socket_iptos; - bool socket_nodelay; - - /* Bookkeeping */ - struct uring_ev *uring; - struct server_cfg_monitor *server_cfg_monitor; - struct signal_ev *signal; - struct announce *announce; - struct ptimer *ptimer; - struct igmp *igmp; - struct idle *idle; - struct uring_task task; - struct list_head servers; -}; - -#endif diff --git a/minecproxy/meson.build b/minecproxy/meson.build index 155840c..3fc6b48 100644 --- a/minecproxy/meson.build +++ b/minecproxy/meson.build @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 minecproxy_sources = [ - 'main.c', + 'minecproxy.c', 'uring.c', 'signal-handler.c', 'server.c', diff --git a/minecproxy/minecproxy.c b/minecproxy/minecproxy.c new file mode 100644 index 0000000..7448943 --- /dev/null +++ b/minecproxy/minecproxy.c @@ -0,0 +1,693 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "minecproxy.h" +#include "signal-handler.h" +#include "uring.h" +#include "shared/config-parser.h" +#include "server.h" +#include "server-config.h" +#include "announce.h" +#include "shared/systemd.h" +#include "igmp.h" +#include "idle.h" +#include "ptimer.h" +#include "config.h" +#include "minecproxy-config-options.h" + +/* Global */ +struct cfg *cfg = NULL; +bool exiting = false; + +/* Local */ +static bool daemonize = false; +static FILE *log_file = NULL; +static const char *log_file_path = NULL; + +static void set_logging_type(bool *use_colors, bool *sd_daemon) +{ + int fd; + const char *e; + + /* assume we're not launched by systemd when daemonized */ + if (daemonize) { + *sd_daemon = false; + *use_colors = false; + return; + } + + if (log_file) { + *sd_daemon = false; + *use_colors = false; + return; + } + + if (getenv("NO_COLOR")) { + *sd_daemon = false; + *use_colors = false; + return; + } + + fd = fileno(stderr); + if (fd < 0) { + /* Umm... */ + *sd_daemon = true; + *use_colors = false; + return; + } + + if (!isatty(fd)) { + *sd_daemon = true; + *use_colors = false; + return; + } + + /* systemd wouldn't normally set TERM */ + e = getenv("TERM"); + if (!e) { + *sd_daemon = true; + *use_colors = false; + return; + } + + if (streq(e, "dumb")) { + *sd_daemon = false; + *use_colors = false; + return; + } + + *sd_daemon = false; + *use_colors = true; +} + +static void msg(enum debug_lvl lvl, const char *fmt, va_list ap) +{ + static bool first = true; + static bool sd_daemon; + const char *color; + const char *sd_lvl; + + assert_return(lvl != 0 && !empty_str(fmt) && ap); + + if (first) { + bool use_colors; + + set_logging_type(&use_colors, &sd_daemon); + if (use_colors) + enable_colors(); + + first = false; + } + + switch (lvl) { + case DBG_ERROR: + sd_lvl = SD_ERR; + color = ansi_red; + break; + case DBG_VERBOSE: + sd_lvl = SD_INFO; + color = NULL; + break; + case DBG_INFO: + sd_lvl = SD_NOTICE; + color = NULL; + break; + default: + sd_lvl = SD_DEBUG; + color = ansi_grey; + break; + } + + if (sd_daemon) + fprintf(stderr, "%s", sd_lvl); + else if (color) + fprintf(stderr, "%s", color); + + vfprintf(log_file ? log_file : stderr, fmt, ap); + + if (color) + fprintf(stderr, "%s", ansi_normal); +} + +void __debug(enum debug_lvl lvl, const char *fmt, ...) +{ + va_list ap; + + assert_return(lvl != 0 && !empty_str(fmt)); + + va_start(ap, fmt); + msg(lvl, fmt, ap); + va_end(ap); +} + +_noreturn_ void __die(const char *fmt, ...) +{ + va_list ap; + + if (!empty_str(fmt)) { + va_start(ap, fmt); + msg(DBG_ERROR, fmt, ap); + va_end(ap); + } else + error("fmt not set"); + + sd_notifyf(0, "STATUS=Error, shutting down"); + exit(EXIT_FAILURE); +} + +static void cfg_free(struct uring_task *task) +{ + struct cfg *xcfg = container_of(task, struct cfg, task); + + assert_return(task && xcfg == cfg); + + debug(DBG_SIG, "called"); + systemd_delete(); + xfree(cfg->igmp_iface); + cfg->igmp_iface = NULL; + xfree(cfg->data_real_path); + cfg->data_real_path = NULL; + xfree(cfg->cfg_real_path); + cfg->cfg_real_path = NULL; + if (cfg->data_dir) { + closedir(cfg->data_dir); + cfg->data_dir = NULL; + } + if (cfg->cfg_dir) { + closedir(cfg->cfg_dir); + cfg->cfg_dir = NULL; + } + exiting = true; + /* The cfg struct is free:d in main() */ +} + +static void cfg_read() +{ + char buf[4096]; + char *pos; + size_t off; + size_t r; + unsigned lineno; + _cleanup_close_ int fd = -1; + _cleanup_fclose_ FILE *cfgfile = NULL; + + assert_return(cfg); + + fd = openat(dirfd(cfg->cfg_dir), MINECPROXY_CFG_FILE, + O_RDONLY | O_CLOEXEC | O_NOCTTY); + if (fd < 0) + return; + + cfgfile = fdopen(fd, "re"); + if (!cfgfile) + return; + + fd = -1; + debug(DBG_CFG, "opened main config file %s/%s", cfg->cfg_real_path, + MINECPROXY_CFG_FILE); + + for (off = 0; off < sizeof(buf); off += r) { + r = fread(buf + off, 1, sizeof(buf) - off, cfgfile); + if (r == 0) + break; + } + + if (off >= sizeof(buf) - 1) + die("main config file %s/%s too large", cfg->cfg_real_path, + MINECPROXY_CFG_FILE); + + buf[off] = '\0'; + pos = buf; + + if (!config_parse_header(MINECPROXY_CFG_HEADER, &pos, &lineno)) + die("main config file %s/%s missing/invalid header", + cfg->cfg_real_path, MINECPROXY_CFG_FILE); + + while (true) { + int key; + const char *keyname; + struct cfg_value value; + const char *error; + + if (!config_parse_line(MINECPROXY_CFG_FILE, &pos, + mcfg_key_map, &key, &keyname, + &value, false, &lineno, &error)) + break; + + if (key == MCFG_KEY_INVALID) + die("main config file %s/%s invalid: line %u: %s", + cfg->cfg_real_path, MINECPROXY_CFG_FILE, lineno, + error); + + debug(DBG_CFG, "main cfg: key %s", keyname); + + switch (key) { + case MCFG_KEY_IGMP: + cfg->do_igmp = value.boolean; + break; + + case MCFG_KEY_IGMP_IFACE: + cfg->igmp_iface = xstrdup(value.str); + if (!cfg->igmp_iface) + die("xstrdup: %m"); + + break; + + case MCFG_KEY_ANNOUNCE_INTERVAL: + cfg->announce_interval = value.uint16; + break; + + case MCFG_KEY_PROXY_CONN_INTERVAL: + cfg->proxy_connection_interval = value.uint16; + break; + + case MCFG_KEY_PROXY_CONN_ATTEMPTS: + cfg->proxy_connection_attempts = value.uint16; + break; + + case MCFG_KEY_SOCKET_DEFER: + cfg->socket_defer = value.boolean; + break; + + case MCFG_KEY_SOCKET_FREEBIND: + cfg->socket_freebind = value.boolean; + break; + + case MCFG_KEY_SOCKET_KEEPALIVE: + cfg->socket_keepalive = value.boolean; + break; + + case MCFG_KEY_SOCKET_IPTOS: + cfg->socket_iptos = value.boolean; + break; + + case MCFG_KEY_SOCKET_NODELAY: + cfg->socket_nodelay = value.boolean; + break; + + case MCFG_KEY_INVALID: + default: + die("main config file invalid"); + } + } +} + +/* clang-format off */ +const struct { + const char *name; + unsigned val; +} debug_category_str[] = { + { + .name = "config", + .val = DBG_CFG, + },{ + .name = "refcount", + .val = DBG_REF, + },{ + .name = "malloc", + .val = DBG_MALLOC, + },{ + .name = "announce", + .val = DBG_ANN, + },{ + .name = "signal", + .val = DBG_SIG, + },{ + .name = "uring", + .val = DBG_UR, + },{ + .name = "server", + .val = DBG_SRV, + },{ + .name = "proxy", + .val = DBG_PROXY, + },{ + .name = "rcon", + .val = DBG_RCON, + },{ + .name = "idle", + .val = DBG_IDLE, + },{ + .name = "igmp", + .val = DBG_IGMP, + },{ + .name = "systemd", + .val = DBG_SYSD, + },{ + .name = "dns", + .val = DBG_DNS, + },{ + .name = "timer", + .val = DBG_TIMER, + },{ + .name = NULL, + .val = 0, + } +}; +/* clang-format on */ + +_noreturn_ static void usage(bool invalid) +{ + if (invalid) + info("Invalid option(s)"); + + info("Usage: %s [OPTIONS]\n" + "\n" + "Valid options:\n" + " -c, --cfgdir=DIR\tuse DIR for configuration files\n" + " -C, --datadir=DIR\tuse DIR for server data\n" + " -u, --user=USER\trun as USER\n" + " -D, --daemonize\trun in daemon mode (disables stderr output)\n" + " -l, --logfile=FILE\tlog to FILE instead of stderr\n" + " -h, --help\t\tprint this information\n" + " -v, --verbose\t\tenable verbose logging\n" + " -d, --debug=CATEGORY\tenable debugging for CATEGORY\n" + "\t\t\t(use \"list\" to see available categories,\n" + "\t\t\t or \"all\" to enable all categories)\n", + program_invocation_short_name); + + exit(invalid ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static void cfg_init(int argc, char **argv) +{ + int c; + unsigned i; + + assert_die(argc > 0 && argv, "invalid arguments"); + + cfg = zmalloc(sizeof(*cfg)); + if (!cfg) + die("malloc: %m"); + + uring_task_init(&cfg->task, "main", NULL, cfg_free); + INIT_LIST_HEAD(&cfg->servers); + + cfg->cfg_path = NULL; + cfg->cfg_real_path = NULL; + cfg->cfg_dir = NULL; + cfg->data_path = NULL; + cfg->data_real_path = NULL; + cfg->data_dir = NULL; + cfg->announce_interval = DEFAULT_ANNOUNCE_INTERVAL; + cfg->proxy_connection_interval = DEFAULT_PROXY_CONN_INTERVAL; + cfg->proxy_connection_attempts = DEFAULT_PROXY_CONN_ATTEMPTS; + cfg->socket_defer = DEFAULT_SOCKET_DEFER; + cfg->socket_freebind = DEFAULT_SOCKET_FREEBIND; + cfg->socket_keepalive = DEFAULT_SOCKET_KEEPALIVE; + cfg->socket_iptos = DEFAULT_SOCKET_IPTOS; + cfg->socket_nodelay = DEFAULT_SOCKET_NODELAY; + cfg->uid = geteuid(); + cfg->gid = getegid(); + + while (true) { + int option_index = 0; + /* clang-format off */ + static struct option long_options[] = { + { "cfgdir", required_argument, 0, 'c' }, + { "datadir", required_argument, 0, 'C' }, + { "user", required_argument, 0, 'u' }, + { "daemonize", no_argument, 0, 'D' }, + { "logfile", required_argument, 0, 'l' }, + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, 0, 'v' }, + { "debug", required_argument, 0, 'd' }, + { 0, 0, 0, 0 } + }; + /* clang-format on */ + + c = getopt_long(argc, argv, ":c:C:u:Dl:hvd:", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + cfg->cfg_path = optarg; + break; + + case 'C': + cfg->data_path = optarg; + break; + + case 'v': + debug_mask |= DBG_VERBOSE; + break; + + case 'D': + daemonize = true; + break; + + case 'l': + log_file_path = optarg; + break; + + case 'u': { + struct passwd *pwd; + + errno = 0; + pwd = getpwnam(optarg); + if (!pwd) { + if (errno == 0) + errno = ESRCH; + if (errno == ESRCH) + die("failed to find user %s", optarg); + else + die("failed to find user %s (%m)", + optarg); + } + + debug(DBG_CFG, "asked to execute with uid %ji gid %ji", + (intmax_t)pwd->pw_uid, (intmax_t)pwd->pw_gid); + cfg->uid = pwd->pw_uid; + cfg->gid = pwd->pw_gid; + break; + } + + case 'd': + if (strcaseeq(optarg, "all")) { + debug_mask = ~0; + break; + } else if (strcaseeq(optarg, "list")) { + info("Debug categories:"); + info(" * all"); + for (i = 0; debug_category_str[i].name; i++) + info(" * %s", + debug_category_str[i].name); + exit(EXIT_FAILURE); + } + + for (i = 0; debug_category_str[i].name; i++) { + if (strcaseeq(optarg, + debug_category_str[i].name)) + break; + } + + if (!debug_category_str[i].name) + usage(true); + + debug_mask |= DBG_VERBOSE; + debug_mask |= debug_category_str[i].val; + break; + + case 'h': + usage(false); + + default: + usage(true); + } + } + + if (optind < argc) + usage(true); +} + +static void cfg_apply() +{ + if (cfg->uid == 0 || cfg->gid == 0) + /* This catches both -u root and running as root without -u */ + die("Execution as root is not supported (use -u )"); + + capng_clear(CAPNG_SELECT_BOTH); + if (capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_RAW, CAP_NET_BIND_SERVICE, -1)) + die("capng_updatev failed"); + + if (geteuid() != cfg->uid) { + if (capng_change_id(cfg->uid, cfg->gid, + CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) + die("capng_change_id failed"); + } else { + /* + * This can fail if any of the caps are lacking, but it'll + * be re-checked later. + */ + capng_apply(CAPNG_SELECT_BOTH); + setgroups(0, NULL); + } + + if (daemonize) { + if (daemon(1, 0) < 0) + die("daemon() failed: %m"); + } + + if (log_file_path) { + log_file = fopen(log_file_path, "ae"); + if (!log_file) + die("fopen(%s) failed: %m", log_file_path); + } + + /* + * Do this after caps have been dropped to make sure we're not + * accessing a directory we should have permissions to. + */ + cfg->cfg_dir = open_cfg_dir(cfg->cfg_path, &cfg->cfg_real_path); + if (!cfg->cfg_dir) + die("Unable to open configuration directory"); + + cfg->data_dir = open_data_dir(cfg->data_path, &cfg->data_real_path); + if (!cfg->data_dir) + die("Unable to open server directory"); + + if (fchdir(dirfd(cfg->data_dir))) + die("Unable to chdir to server directory: %m"); + + if (debug_enabled(DBG_VERBOSE)) { + char *wd; + + wd = get_current_dir_name(); + verbose("Working directory: %s", wd ? wd : ""); + free(wd); + } +} + +void dump_tree() +{ + struct server *server; + + if (!debug_enabled(DBG_REF)) + return; + + info("\n\n"); + info("Dumping Tree"); + info("============"); + uring_task_refdump(&cfg->task); + uring_refdump(); + signal_refdump(); + ptimer_refdump(); + idle_refdump(); + igmp_refdump(); + announce_refdump(); + server_cfg_monitor_refdump(); + list_for_each_entry(server, &cfg->servers, list) + server_refdump(server); + info("============"); + info("\n\n"); +} + +int main(int argc, char **argv) +{ + struct server *server; + unsigned server_count; + struct rlimit old_rlimit; + + debug_mask = DBG_ERROR | DBG_INFO; + + cfg_init(argc, argv); + + cfg_apply(); + + cfg_read(); + + /* + * In the splice case we use 6 fds per proxy connection... + */ + if (prlimit(0, RLIMIT_NOFILE, NULL, &old_rlimit) == 0) { + struct rlimit new_rlimit; + + new_rlimit.rlim_cur = old_rlimit.rlim_max; + new_rlimit.rlim_max = old_rlimit.rlim_max; + + if (prlimit(0, RLIMIT_NOFILE, &new_rlimit, NULL) == 0) + debug(DBG_MALLOC, "prlimit(NOFILE): %u/%u -> %u/%u", + (unsigned)old_rlimit.rlim_cur, + (unsigned)old_rlimit.rlim_max, + (unsigned)new_rlimit.rlim_cur, + (unsigned)new_rlimit.rlim_cur); + } + + uring_init(); + + ptimer_init(); + + igmp_init(); + + /* Drop CAP_NET_RAW (if we have it), only used for igmp */ + capng_clear(CAPNG_SELECT_BOTH); + if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_BIND_SERVICE)) + die("capng_update failed"); + + if (capng_apply(CAPNG_SELECT_BOTH)) { + /* Try clearing all caps, shouldn't fail */ + capng_clear(CAPNG_SELECT_BOTH); + if (capng_apply(CAPNG_SELECT_BOTH)) + die("capng_apply failed"); + } + + signal_init(); + + server_cfg_monitor_init(); + + announce_init(); + + if (!cfg->igmp) + announce_start(0); + + idle_init(); + + uring_task_put(&cfg->task); + + server_count = 0; + list_for_each_entry(server, &cfg->servers, list) + server_count++; + + sd_notifyf(0, + "READY=1\n" + "STATUS=Running, %u server configurations loaded\n" + "MAINPID=%lu", + server_count, (unsigned long)getpid()); + + info("mcproxy (%s) started, %u server configurations loaded", VERSION, + server_count); + + uring_event_loop(); + + verbose("Exiting"); + + xfree(cfg); + cfg = NULL; + + if (debug_enabled(DBG_MALLOC)) + debug_resource_usage(); + + fflush(stdout); + fflush(stderr); + exit(EXIT_SUCCESS); +} diff --git a/minecproxy/minecproxy.h b/minecproxy/minecproxy.h new file mode 100644 index 0000000..adb82c6 --- /dev/null +++ b/minecproxy/minecproxy.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef foominecproxyhfoo +#define foominecproxyhfoo + +#include +#include + +struct cfg; +struct uring_task; + +#include "misc.h" +#include "shared/utils.h" + +extern struct cfg *cfg; +extern bool exiting; + +void dump_tree(); + +/* To save typing in all the function definitions below */ +typedef void (*utask_cb_t)(struct uring_task *, int res); +typedef int (*rutask_cb_t)(struct uring_task *, int res); + +struct uring_task_buf { + char buf[4096]; + size_t len; + size_t done; + struct iovec iov; + struct msghdr msg; +}; + +struct uring_task { + const char *name; + int refcount; /* signed to catch refcount bugs */ + int fd; + struct uring_task *parent; + void (*free)(struct uring_task *); + bool dead; + struct uring_task_buf *tbuf; + + /* called once or repeatedly until is_complete_cb is satisfied */ + utask_cb_t cb; + + /* returns: 0 = not complete; < 0 = error; > 0 = complete */ + rutask_cb_t is_complete_cb; + + /* called once tbuf processing is done */ + utask_cb_t final_cb; + + /* used for recvmsg/sendmsg */ + struct saddr saddr; + void *priv; +}; + +struct cfg { + /* Options */ + const char *cfg_path; + char *cfg_real_path; + DIR *cfg_dir; + const char *data_path; + char *data_real_path; + DIR *data_dir; + uid_t uid; + gid_t gid; + bool do_igmp; + char *igmp_iface; + bool splice_supported; + uint16_t announce_interval; + uint16_t proxy_connection_interval; + uint16_t proxy_connection_attempts; + bool socket_defer; + bool socket_freebind; + bool socket_keepalive; + bool socket_iptos; + bool socket_nodelay; + + /* Bookkeeping */ + struct uring_ev *uring; + struct server_cfg_monitor *server_cfg_monitor; + struct signal_ev *signal; + struct announce *announce; + struct ptimer *ptimer; + struct igmp *igmp; + struct idle *idle; + struct uring_task task; + struct list_head servers; +}; + +#endif diff --git a/minecproxy/misc.c b/minecproxy/misc.c index 5687a3b..fa9d615 100644 --- a/minecproxy/misc.c +++ b/minecproxy/misc.c @@ -14,7 +14,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "misc.h" #include "uring.h" diff --git a/minecproxy/ptimer.c b/minecproxy/ptimer.c index 1ef7609..e84af24 100644 --- a/minecproxy/ptimer.c +++ b/minecproxy/ptimer.c @@ -5,7 +5,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "ptimer.h" diff --git a/minecproxy/server-config.c b/minecproxy/server-config.c index 4e77716..4f8789f 100644 --- a/minecproxy/server-config.c +++ b/minecproxy/server-config.c @@ -10,7 +10,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "shared/config-parser.h" #include "server.h" diff --git a/minecproxy/server-proxy.c b/minecproxy/server-proxy.c index ac41594..d357396 100644 --- a/minecproxy/server-proxy.c +++ b/minecproxy/server-proxy.c @@ -8,7 +8,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "ptimer.h" #include "server.h" diff --git a/minecproxy/server-rcon.c b/minecproxy/server-rcon.c index a728bab..81a526c 100644 --- a/minecproxy/server-rcon.c +++ b/minecproxy/server-rcon.c @@ -11,7 +11,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "server.h" #include "server-rcon.h" diff --git a/minecproxy/server.c b/minecproxy/server.c index 341a353..7e6aa84 100644 --- a/minecproxy/server.c +++ b/minecproxy/server.c @@ -11,7 +11,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" #include "ptimer.h" #include "server.h" diff --git a/minecproxy/signal-handler.c b/minecproxy/signal-handler.c index 56d016f..8c70f97 100644 --- a/minecproxy/signal-handler.c +++ b/minecproxy/signal-handler.c @@ -5,7 +5,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "signal-handler.h" #include "uring.h" #include "server.h" diff --git a/minecproxy/uring.c b/minecproxy/uring.c index d16abac..6748fca 100644 --- a/minecproxy/uring.c +++ b/minecproxy/uring.c @@ -7,7 +7,7 @@ #include #include -#include "main.h" +#include "minecproxy.h" #include "uring.h" struct uring_ev { -- cgit v1.2.3