summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-23 16:25:36 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-23 16:25:36 +0200
commit8c27290245b7bcc7cd2f72f3b4a7562294b43bbe (patch)
tree54bae7909a94bfc598df7b88d9794742daf0bb31 /main.c
parent973ae757342b91e3e6aafd07e0c0a24af84aad98 (diff)
Split directories better
Diffstat (limited to 'main.c')
-rw-r--r--main.c740
1 files changed, 0 insertions, 740 deletions
diff --git a/main.c b/main.c
deleted file mode 100644
index 047e70e..0000000
--- a/main.c
+++ /dev/null
@@ -1,740 +0,0 @@
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdbool.h>
-#include <getopt.h>
-#include <systemd/sd-daemon.h>
-#include <cap-ng.h>
-#include <pwd.h>
-#include <grp.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-
-#include "main.h"
-#include "signal-handler.h"
-#include "uring.h"
-#include "config-parser.h"
-#include "server.h"
-#include "server-config.h"
-#include "announce.h"
-#include "systemd.h"
-#include "igmp.h"
-#include "idle.h"
-#include "ptimer.h"
-#include <config.h>
-
-/* Global */
-struct cfg *cfg = NULL;
-bool exiting = false;
-unsigned debug_mask = DBG_ERROR | DBG_INFO;
-
-/* Local */
-static bool daemonize = false;
-static FILE *log_file = NULL;
-static const char *log_file_path = NULL;
-
-#define ANSI_RED "\x1B[0;31m"
-#define ANSI_GREEN "\x1B[0;32m"
-#define ANSI_YELLOW "\x1B[0;33m"
-#define ANSI_BLUE "\x1B[0;34m"
-#define ANSI_MAGENTA "\x1B[0;35m"
-#define ANSI_GREY "\x1B[0;38;5;245m"
-#define ANSI_NORMAL "\x1B[0m"
-
-static void
-msg(enum debug_lvl lvl, const char *fmt, va_list ap)
-{
- static bool first = true;
- static bool use_colors = false;
- static bool sd_daemon = false;
- const char *color;
- const char *sd_lvl;
-
- assert_return(lvl != 0 && !empty_str(fmt) && ap);
-
- while (first) {
- int fd;
- const char *e;
-
- first = false;
-
- /* assume we're not launched by systemd when daemonized */
- if (daemonize) {
- sd_daemon = false;
- use_colors = false;
- break;
- }
-
- if (log_file) {
- sd_daemon = false;
- use_colors = false;
- break;
- }
-
- if (getenv("NO_COLOR")) {
- sd_daemon = false;
- use_colors = false;
- break;
- }
-
- fd = fileno(stderr);
- if (fd < 0) {
- /* Umm... */
- sd_daemon = true;
- use_colors = false;
- break;
- }
-
- if (!isatty(fd)) {
- sd_daemon = true;
- use_colors = false;
- break;
- }
-
- /* systemd wouldn't normally set TERM */
- e = getenv("TERM");
- if (!e) {
- sd_daemon = true;
- use_colors = false;
- break;
- }
-
- if (streq(e, "dumb")) {
- sd_daemon = false;
- use_colors = false;
- break;
- }
-
- sd_daemon = false;
- use_colors = true;
- }
-
- switch (lvl) {
- case DBG_ERROR:
- sd_lvl = SD_ERR;
- color = use_colors ? ANSI_RED : NULL;
- 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 = use_colors ? ANSI_GREY : NULL;
- break;
- }
-
- if (sd_daemon)
- fprintf(stderr, sd_lvl);
- else if (color)
- fprintf(stderr, color);
-
- vfprintf(log_file ? log_file : stderr, fmt, ap);
-
- if (color)
- fprintf(stderr, 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);
-}
-
-__attribute__((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(cfg);
- xfree(cfg->igmp_iface);
- cfg->igmp_iface = NULL;
- exiting = true;
- /* The cfg struct is free:d in main() */
-}
-
-enum mcfg_keys {
- MCFG_KEY_INVALID = 0,
- MCFG_KEY_IGMP,
- MCFG_KEY_IGMP_IFACE,
- MCFG_KEY_ANN_INTERVAL,
- MCFG_KEY_PROXY_CONN_INTERVAL,
- MCFG_KEY_PROXY_CONN_ATTEMPTS,
- MCFG_KEY_SOCKET_DEFER,
- MCFG_KEY_SOCKET_FREEBIND,
- MCFG_KEY_SOCKET_KEEPALIVE,
- MCFG_KEY_SOCKET_IPTOS,
- MCFG_KEY_SOCKET_NODELAY,
-};
-
-struct cfg_key_value_map mcfg_key_map[] = {
- {
- .key_name = "igmp",
- .key_value = MCFG_KEY_IGMP,
- .value_type = CFG_VAL_TYPE_BOOL,
- }, {
- .key_name = "igmp_iface",
- .key_value = MCFG_KEY_IGMP_IFACE,
- .value_type = CFG_VAL_TYPE_STRING,
- }, {
- .key_name = "announce_interval",
- .key_value = MCFG_KEY_ANN_INTERVAL,
- .value_type = CFG_VAL_TYPE_UINT16,
- }, {
- .key_name = "proxy_connection_interval",
- .key_value = MCFG_KEY_PROXY_CONN_INTERVAL,
- .value_type = CFG_VAL_TYPE_UINT16,
- }, {
- .key_name = "proxy_connection_attempts",
- .key_value = MCFG_KEY_PROXY_CONN_ATTEMPTS,
- .value_type = CFG_VAL_TYPE_UINT16,
- }, {
- .key_name = "socket_defer",
- .key_value = MCFG_KEY_SOCKET_DEFER,
- .value_type = CFG_VAL_TYPE_BOOL,
- }, {
- .key_name = "socket_freebind",
- .key_value = MCFG_KEY_SOCKET_FREEBIND,
- .value_type = CFG_VAL_TYPE_BOOL,
- }, {
- .key_name = "socket_keepalive",
- .key_value = MCFG_KEY_SOCKET_KEEPALIVE,
- .value_type = CFG_VAL_TYPE_BOOL,
- }, {
- .key_name = "socket_iptos",
- .key_value = MCFG_KEY_SOCKET_IPTOS,
- .value_type = CFG_VAL_TYPE_BOOL,
- }, {
- .key_name = "socket_nodelay",
- .key_value = MCFG_KEY_SOCKET_NODELAY,
- .value_type = CFG_VAL_TYPE_BOOL,
- }, {
- .key_name = NULL,
- .key_value = MCFG_KEY_INVALID,
- .value_type = CFG_VAL_TYPE_INVALID,
- }
-};
-
-static void
-cfg_read()
-{
- FILE *cfgfile;
- const char *path;
- char buf[4096];
- char *pos = buf;
- size_t rd = 0;
- size_t r;
-
- assert_return(cfg);
-
- if (cfg->cfg_file)
- path = cfg->cfg_file;
- else
- path = DEFAULT_MAIN_CFG_FILE;
-
- cfgfile = fopen(path, "re");
- if (!cfgfile) {
- /* ENOENT is only an error with an explicitly set path */
- if (errno == ENOENT && !cfg->cfg_file)
- return;
- else if (errno == ENOENT)
- die("main config file (%s) missing", path);
- else
- die("fopen(%s): %m", path);
- }
-
- debug(DBG_CFG, "opened main config file (%s)", path);
-
- while (rd < sizeof(buf)) {
- r = fread(pos, 1, sizeof(buf) - rd - 1, cfgfile);
- if (r == 0)
- break;
- rd += r;
- pos += r;
- }
-
- if (rd == 0)
- die("main config file (%s) zero size", path);
-
- if (rd >= sizeof(buf))
- die("main config file (%s) too large", path);
-
- fclose(cfgfile);
- *pos = '\0';
- pos = buf;
-
- if (!config_parse_header(path, "mcproxy", &pos))
- die("main config file (%s) invalid", path);
-
- while (true) {
- int key;
- const char *keyname;
- struct cfg_value value;
-
- if (!config_parse_line(path, &pos, mcfg_key_map,
- &key, &keyname, &value))
- break;
-
- if (key == MCFG_KEY_INVALID)
- die("main config file (%s) invalid", path);
-
- 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_ANN_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 (%s) invalid", path);
- }
- }
-}
-
-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,
- }
-};
-
-__attribute__((noreturn)) static void
-usage(int argc, char **argv, bool invalid)
-{
- if (invalid)
- info("Invalid option(s)");
-
- info("Usage: %s [OPTIONS]\n"
- "\n"
- "Valid options:\n"
- " -c, --cfgdir=DIR\tlook for configuration files in DIR\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",
- argv ? argv[0] : "mcproxy");
-
- 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);
- list_init(&cfg->servers);
-
- cfg->cfg_dir = DEFAULT_CFG_DIR;
- 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;
- static struct option long_options[] = {
- { "cfgdir", required_argument, 0, 'c' },
- { "cfgfile", 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 }
- };
-
- c = getopt_long(argc, argv, ":c:C:u:Dl:hvd:",
- long_options, &option_index);
- if (c == -1)
- break;
-
- switch (c) {
- case 'c':
- cfg->cfg_dir = optarg;
- break;
-
- case 'C':
- cfg->cfg_file = 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(argc, argv, true);
-
- debug_mask |= DBG_VERBOSE;
- debug_mask |= debug_category_str[i].val;
- break;
-
- case 'h':
- usage(argc, argv, false);
-
- default:
- usage(argc, argv, true);
- }
-
- }
-
- if (optind < argc)
- usage(argc, argv, 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 <someuser>)");
-
- 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.
- */
- if (chdir(cfg->cfg_dir))
- die("chdir(%s): %m", cfg->cfg_dir);
-
- if (debug_enabled(DBG_VERBOSE)) {
- char *wd;
-
- wd = get_current_dir_name();
- verbose("Working directory: %s", wd ? wd : "<unknown>");
- 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;
-
- cfg_init(argc, argv);
-
- cfg_apply();
-
- cfg_read();
-
- /*
- * In the splice case we use 4 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);
-}