From 30e19ad39ccc099b1b41f4df9be21d8d25cb8f45 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Fri, 26 Jun 2020 00:25:30 +0200 Subject: Bring some sanity to cmd parsing --- minecctl/minecctl.c | 380 ++++++++++++++++++++++++---------------------------- 1 file changed, 175 insertions(+), 205 deletions(-) (limited to 'minecctl/minecctl.c') diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index ed91fdf..6d60a32 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -137,7 +137,7 @@ dump_config() assert_die(cfg, "cfg not set"); info("Configuration:"); - info("pwd : %s", cfg->password); + info("password : %s", cfg->password); info("cfgdir : %s", cfg->cfgdir); info("addrstr : %s", cfg->addrstr); info("cmdstr : %s", cfg->cmdstr); @@ -150,12 +150,9 @@ dump_config() } static void -parse_server_config(char *buf, const char *filename, - struct list_head *addrs) +parse_server_config(char *buf, const char *filename) { - list_init(addrs); - - assert_die(buf && filename && addrs && cfg, "invalid arguments"); + assert_die(buf && filename && cfg, "invalid arguments"); if (!config_parse_header(SERVER_CFG_HEADER, &buf)) die("Unable to parse %s: invalid/missing header", filename); @@ -172,25 +169,36 @@ parse_server_config(char *buf, const char *filename, switch (key) { case SCFG_KEY_RCON: - list_replace(&value.saddrs, addrs); + if (!list_empty(&cfg->addrs)) + die("rcon address defined twice in %s", filename); + list_replace(&value.saddrs, &cfg->addrs); break; case SCFG_KEY_RCON_PASSWORD: if (!cfg->password) cfg->password = xstrdup(value.str); break; + case SCFG_KEY_REMOTE: + if (!list_empty(&cfg->mcaddrs)) + die("rcon address defined twice in %s", filename); + list_replace(&value.saddrs, &cfg->mcaddrs); default: continue; } - if (cfg->password && !list_empty(addrs)) + if (cfg->password && + !list_empty(&cfg->addrs) && + !list_empty(&cfg->mcaddrs)) break; } if (!cfg->password) verbose("rcon password not found in %s", filename); - if (list_empty(addrs)) - die("rcon address not found in %s", filename); + if (list_empty(&cfg->addrs)) + verbose("rcon address not found in %s", filename); + + if (list_empty(&cfg->mcaddrs)) + verbose("mc server address not found in %s", filename); } static void @@ -229,79 +237,74 @@ read_server_config() buf[off] = '\0'; close(fd); - parse_server_config(buf, cfg->server->filename, &cfg->addrs); + parse_server_config(buf, cfg->server->filename); + dump_config(); } _noreturn_ static void -usage(bool invalid) +usage(bool no_error) { - if (invalid) - error("Invalid option(s)"); - info("Usage: %s [OPTION...] COMMAND\n" "\n" "Valid options:\n" - " -p, --password=PWD use PWD as the rcon password\n" - " (or use environment variable RCON_PASSWORD)\n" - " -a, --address=ADDR connect to rcon at ADDR\n" - " (or use environment variable RCON_ADDRESS)\n" - " -A, --mcaddress=ADDR connect to Minecraft server at ADDR\n" - " (potentially used for ping, status, pcount)\n" - " -c, --cfgdir=DIR look for server configurations in DIR\n" - " (default: %s)\n" - " -f, --force stop server even if it has players\n" - " -v, --verbose enable extra logging\n" - " -h, --help print this information\n" + " -p, --password=PWD use PWD as the rcon password\n" + " (or use environment variable RCON_PASSWORD)\n" + " -a, --rcon-address=ADDR connect to rcon at ADDR\n" + " (or use environment variable RCON_ADDRESS)\n" + " -m, --mc-address=ADDR connect to Minecraft server at ADDR\n" + " (only relevant for some commands, can also\n" + " use environment variable MC_ADDRESS)\n" + " -c, --cfgdir=DIR look for server configurations in DIR\n" + " (default: %s)\n" + " -f, --force stop server even if it has players\n" + " -v, --verbose enable extra logging\n" + " -h, --help print this information\n" "\n" "Valid commands:\n" - " list list known servers\n" - " status [SERVER] show status of SERVER (or all known servers)\n" - " ping [SERVER] check if SERVER is running\n" - " stop [SERVER] stop SERVER\n" - " stopall stop all known servers\n" - " pcount [SERVER] get player count for SERVER\n" - " console [SERVER] provide an interactive command line for SERVER\n" - " cmd [SERVER] CMD send CMD to SERVER\n" - " [SERVER] CMD shorthand for \"cmd [SERVER] CMD\"\n" - " [SERVER] shorthand for \"console [SERVER]\"\n" + " list list known servers\n" + " status [SERVER] show status of SERVER (or all known servers)\n" + " ping [SERVER] check if SERVER is running\n" + " stop [SERVER] stop SERVER\n" + " stopall stop all known servers\n" + " pcount [SERVER] get player count for SERVER\n" + " console [SERVER] interactive command line for SERVER\n" + " cmd [SERVER] CMD send CMD to SERVER\n" + " [SERVER] CMD shorthand for \"cmd [SERVER] CMD\"\n" + " [SERVER] shorthand for \"console [SERVER]\"\n" "\n" "Note: if ADDR is given as an option, SERVER must be omitted from\n" - " the command and vice versa.\n" - "\n", + " the command and vice versa.\n", program_invocation_short_name, DEFAULT_CFG_DIR); - exit(invalid ? EXIT_FAILURE : EXIT_SUCCESS); + exit(no_error ? EXIT_FAILURE : EXIT_SUCCESS); } static char * -combine_args(int index, int remain, char **argv) +strv_join(char * const *strv) { size_t len = 0; - char *result, *pos; - - if (index < 0 || remain < 0) - die("Internal error parsing arguments"); + char *r, *to; - for (int i = index; i < index + remain; i++) - len += strlen(argv[i]) + 1; + for (unsigned i = 0; strv[i]; i++) + len += strlen(strv[i]) + 1; if (len == 0) - die("Internal error parsing arguments"); + return NULL; - len++; - result = zmalloc(len); - pos = result; - for (int i = index; i < index + remain; i++) - pos += sprintf(pos, "%s ", argv[i]); + r = zmalloc(len); + to = r; - pos--; - *pos = '\0'; + for (unsigned i = 0; strv[i]; i++) { + if (i > 0) + *(to++) = ' '; + to = stpcpy(to, strv[i]); + } - return result; + return r; } static void -get_servers() +get_known_servers() { static bool first = true; struct dirent *dent; @@ -344,28 +347,10 @@ do_list(struct cfg *cfg) { struct server *server; - get_servers(); - list_for_each_entry(server, &cfg->known_servers, list) info("%s", server->shortname); } -static bool -is_known_server(const char *name) -{ - struct server *server; - - assert_die(cfg && name, "invalid arguments"); - - get_servers(); - list_for_each_entry(server, &cfg->known_servers, list) { - if (streq(name, server->shortname)) - return true; - } - - return false; -} - static void set_server(const char *name) { @@ -374,7 +359,6 @@ set_server(const char *name) assert_die(cfg, "invalid arguments"); assert_die(!cfg->server, "can't set server twice"); - get_servers(); list_for_each_entry(server, &cfg->known_servers, list) { if (streq(name, server->shortname)) { cfg->server = server; @@ -383,7 +367,7 @@ set_server(const char *name) } error("%s is not a known server", name); - usage(true); + usage(false); } char * @@ -454,7 +438,7 @@ connect_any(struct list_head *addrs, bool may_fail) if (!connected) { if (may_fail) - sfd = -1; + return -1; else die("Failed to connect to remote host"); } @@ -462,166 +446,138 @@ connect_any(struct list_head *addrs, bool may_fail) return sfd; } -static void -parse_verb(int index, int remain, char **argv) +static inline void +get_optional_server_arg(char * const **argv, bool more) { - enum command_args args; - enum commands cmd; + if (!cfg->addrstr) { + if (!**argv) { + error("Missing argument"); + usage(false); + } + set_server(**argv); + (*argv)++; + } - assert_die(index >= 0 && remain >= 0 && argv, "invalid arguments"); + if (!more && **argv) { + error("Too many arguments"); + usage(false); + } +} - cmd = CMD_INVALID; +static void +parse_command(unsigned argc, char * const *argv) +{ + enum commands cmd; - info("start verb, remain %i, index %i", remain, index); - for (int i = index; i < index + remain; i++) - info("arg[%i]: %s", i, argv[i]); + assert_die(argv, "invalid arguments"); - if (remain == 0) { - /* shorthand for console if address is set */ - if (!cfg->addrstr) - usage(true); + /* Shorthand for console if address is set */ + if (argc == 0) { + if (!cfg->addrstr) { + error("Missing options"); + usage(false); + } - cmd = CMD_CONSOLE; - goto out; + cfg->cmd = do_console; + return; } - args = CMD_ARG_INVALID; - for (int i = 0; command_list[i].name; i++) { - if (streq(argv[index], command_list[i].name)) { + cmd = CMD_INVALID; + for (unsigned i = 0; command_list[i].name; i++) { + if (streq(*argv, command_list[i].name)) { cmd = command_list[i].cmd; - args = command_list[i].args; + argv++; break; } } - if (cmd == CMD_INVALID) { - /* maybe shorthand for console: [SERVER] CMD */ - if (cfg->addrstr && is_known_server(argv[index])) { - error("Ambigous command, address set and server specified"); - usage(true); - } else if (cfg->addrstr) { - /* CMD */ - cmd = CMD_COMMAND; - cfg->cmdstr = combine_args(index, remain, argv); - goto out; - } else { - /* SERVER [CMD] */ - set_server(argv[index]); - index++; - remain--; - - if (remain < 1) - cmd = CMD_CONSOLE; - else { - cmd = CMD_COMMAND; - cfg->cmdstr = combine_args(index, remain, argv); - } - goto out; - } - } - - index++; - remain--; - - info("here: index %i, remain %i, args %i", index, remain, args); - - switch (args) { - case CMD_ARG_NONE: - info("here: index %i, remain %i, args %i", index, remain, args); - if (remain != 0) - usage(true); - info("here: index %i, remain %i, args %i", index, remain, args); - break; - - case CMD_ARG_ONE_OPTIONAL: - info("hereY: index %i, remain %i, args %i", index, remain, args); - if (remain == 0 && cfg->addrstr) - break; - else if (remain == 1 && !cfg->addrstr) { - set_server(argv[index]); - index++; - remain--; - break; - } - usage(true); + switch (cmd) { + case CMD_INVALID: + /* shorthand notation? */ + if (!cfg->addrstr) + /* SERVER [CMD...] */ + set_server(*argv++); - case CMD_ARG_AT_LEAST_ONE: - info("hereX: index %i, remain %i, args %i", index, remain, args); - if (remain > 0 && cfg->addrstr) - break; - else if (remain > 1 && !cfg->addrstr) { - set_server(argv[index]); - index++; - remain--; + /* !CMD = console */ + if (!*argv) { + cfg->cmd = do_console; break; } - usage(true); - - case CMD_ARG_INVALID: - info("hereZ: index %i, remain %i, args %i", index, remain, args); - _fallthrough_; - default: - info("hereT: index %i, remain %i, args %i", index, remain, args); - die("Internal cmd parsing error"); - } -out: - switch (cmd) { + /* CMD... */ + cfg->cmdstr = strv_join(argv); + cfg->cmd = do_command; + break; case CMD_LIST: + if (*argv) { + error("Too many options"); + usage(false); + } + cfg->cmd = do_list; + break; + case CMD_STOPALL: + if (*argv) { + error("Too many options"); + usage(false); + } cfg->cmd = do_list; break; case CMD_STATUS: + get_optional_server_arg(&argv, false); cfg->cmd = do_status; break; case CMD_PING: + get_optional_server_arg(&argv, false); cfg->cmd = do_ping; break; case CMD_STOP: + get_optional_server_arg(&argv, false); cfg->cmd = do_stop; break; - case CMD_STOPALL: - cfg->cmd = do_stop_all; - break; case CMD_PCOUNT: + get_optional_server_arg(&argv, false); cfg->cmd = do_pcount; break; case CMD_CONSOLE: + get_optional_server_arg(&argv, false); cfg->cmd = do_console; break; case CMD_COMMAND: + if (!*argv) { + error("Missing options"); + usage(false); + } + + if (!cfg->addrstr) + set_server(*argv++); + + if (!*argv) { + error("Missing options"); + usage(false); + } + + cfg->cmdstr = strv_join(argv); cfg->cmd = do_command; - cfg->cmdstr = combine_args(index, remain, argv); - remain = 0; break; default: - die("Internal cmd parsing error"); + die("Unreachable"); } - if (args == CMD_ARG_NONE && !cfg->server && !cfg->cmdstr) - return; - - if (cfg->addrstr && cfg->server) - usage(true); - dump_config(); - - info("here2: index %i, remain %i, args %i", index, remain, args); - if ((!cfg->addrstr && !cfg->server) || - (cfg->cmdstr && cmd != CMD_COMMAND) || - (!cfg->cmdstr && cmd == CMD_COMMAND) || - (remain != 0) || (cmd == CMD_INVALID)) - die("Internal cmd parsing error"); } static void -parse_cmdline(int argc, char **argv) +parse_cmdline(int argc, char * const *argv) { int c; + char *e; assert_die(argc && argv && cfg, "invalid arguments"); - if (argc < 2) - usage(true); + if (argc < 2) { + error("Missing options"); + usage(false); + } list_init(&cfg->addrs); cfg->cfgdir = DEFAULT_CFG_DIR; @@ -629,17 +585,17 @@ parse_cmdline(int argc, char **argv) while (true) { int option_index = 0; static struct option long_options[] = { - { "password", required_argument, 0, 'p' }, - { "address", required_argument, 0, 'a' }, - { "mcaddress", required_argument, 0, 'A' }, - { "cfgdir", required_argument, 0, 'c' }, - { "verbose", no_argument, 0, 'v' }, - { "force", no_argument, 0, 'f' }, - { "help", no_argument, 0, 'h' }, - { 0, 0, 0, 0 } + { "password", required_argument, 0, 'p' }, + { "rcon-address", required_argument, 0, 'a' }, + { "mc-address", required_argument, 0, 'A' }, + { "cfgdir", required_argument, 0, 'c' }, + { "verbose", no_argument, 0, 'v' }, + { "force", no_argument, 0, 'f' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } }; - c = getopt_long(argc, argv, ":p:a:A:c:fvh", + c = getopt_long(argc, argv, ":p:a:m:c:fvh", long_options, &option_index); if (c == -1) @@ -652,7 +608,7 @@ parse_cmdline(int argc, char **argv) case 'a': cfg->addrstr = xstrdup(optarg); break; - case 'A': + case 'm': cfg->mcaddrstr = xstrdup(optarg); break; case 'c': @@ -665,30 +621,30 @@ parse_cmdline(int argc, char **argv) debug_mask |= DBG_VERBOSE; break; case 'h': - usage(false); - default: usage(true); + default: + error("Invalid options"); + usage(false); } } if (!cfg->password) { - char *e; - e = getenv("RCON_PASSWORD"); if (e) cfg->password = xstrdup(e); } if (!cfg->addrstr) { - char *e; - e = getenv("RCON_ADDRESS"); if (e) cfg->addrstr = xstrdup(e); } - parse_verb(optind, argc - optind, argv); - + if (!cfg->mcaddrstr) { + e = getenv("MC_ADDRESS"); + if (e) + cfg->mcaddrstr = xstrdup(e); + } } int @@ -704,7 +660,15 @@ main(int argc, char **argv) list_init(&cfg->known_servers); parse_cmdline(argc, argv); + info("cmdline parsed"); + dump_config(); + get_known_servers(); + info("known servers loaded"); + dump_config(); + + parse_command(argc - optind, &argv[optind]); + info("command parsed"); dump_config(); if (!cfg->cmd) @@ -733,6 +697,12 @@ main(int argc, char **argv) cfg->cmd(cfg); + if (cfg->password) + explicit_bzero(cfg->password, strlen(cfg->password)); + + xfree(cfg->password); + xfree(cfg->addrstr); + xfree(cfg->mcaddrstr); xfree(cfg); exit(EXIT_SUCCESS); } -- cgit v1.2.3