/* * Copyright (C) 2012-2026 Tobias Brunner * Copyright (C) 2009 Martin Willi * * Copyright (C) secunet Security Networks AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "command.h" #include "pki.h" #define _GNU_SOURCE #include #include #include #include #include #include #include /** * Registered commands. */ static command_t cmds[MAX_COMMANDS]; /** * active command. */ static int active = 0; /** * number of registered commands */ static int registered = 0; /** * help command index */ static int help_idx; static int argc; static char **argv; static options_t *options; /** * Global options used by all subcommands */ static struct option command_opts[(MAX_COMMANDS > MAX_OPTIONS ? MAX_COMMANDS : MAX_OPTIONS) + 1]; /** * Global optstring used by all subcommands */ static char command_optstring[(MAX_COMMANDS > MAX_OPTIONS ? MAX_COMMANDS : MAX_OPTIONS) * 3]; /** * Common options */ static command_option_t shared_options[] = { { "debug", 'v', 1, "set debug level, default: 1" }, { "options", '+', 1, "read command line options from file" }, }; /** * Add a single option to command_opts/command_optstr at the current locations. */ static void build_option(command_option_t *option, int i, int *pos) { command_opts[i].name = option->name; command_opts[i].has_arg = option->arg; command_opts[i].val = option->op; command_optstring[(*pos)++] = option->op; switch (option->arg) { case optional_argument: command_optstring[(*pos)++] = ':'; /* FALL */ case required_argument: command_optstring[(*pos)++] = ':'; /* FALL */ case no_argument: default: break; } } /** * Build command_opts/command_optstr for the active command */ static void build_opts() { int i, j, pos = 0; memset(command_opts, 0, sizeof(command_opts)); memset(command_optstring, 0, sizeof(command_optstring)); if (active == help_idx) { for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) { command_opts[i].name = cmds[i].cmd; command_opts[i].val = cmds[i].op; command_optstring[i] = cmds[i].op; } /* add shared options not as actual option but as commands with * argument (which commands usually don't take) */ if (i > MAX_COMMANDS - countof(shared_options)) { fprintf(stderr, "unable to add global shared options, please " "increase MAX_COMMANDS\n"); } else { for (j = 0, pos = i; j < countof(shared_options); j++, i++) { build_option(&shared_options[j], i, &pos); } } } else { for (i = 0; i < MAX_OPTIONS && cmds[active].options[i].name; i++) { build_option(&cmds[active].options[i], i, &pos); } } } /** * Wrapper around getopt_long() that skippes common options */ int command_getopt(char **arg) { int op; while (TRUE) { op = getopt_long(argc, argv, command_optstring, command_opts, NULL); switch (op) { case '+': case 'v': continue; default: if (arg) { *arg = optarg; } return op; } } } /** * Pre-process options common for all commands */ static bool process_common_opts(bool init) { int prevoptind = optind; bool success = TRUE; /* don't report any errors during this pre-processing */ opterr = 0; while (TRUE) { switch (getopt_long(argc, argv, command_optstring, command_opts, NULL)) { case '+': if (!options->from(options, optarg, &argc, &argv, optind)) { success = FALSE; break; } continue; case 'v': dbg_default_set_level(atoi(optarg)); continue; default: if (init) { /* stop if we found a known command during initialization, * otherwise we'd process e.g. --options twice */ break; } continue; case '?': case EOF: break; } break; } /* restore option parser state after pre-processing */ optind = prevoptind; opterr = 1; return success; } /** * Register a command */ void command_register(command_t command) { int i, j; if (registered == MAX_COMMANDS) { fprintf(stderr, "unable to register command, please increase " "MAX_COMMANDS\n"); return; } for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) { if (cmds[i].op == command.op) { fprintf(stderr, "unable to register command --%s, short option " "conflicts with --%s\n", command.cmd, cmds[i].cmd); return; } } for (i = 0; i < countof(shared_options); i++) { if (shared_options[i].op == command.op) { fprintf(stderr, "unable to register command --%s, short option " "-%c conflicts with --%s\n", command.cmd, command.op, shared_options[i].name); return; } } cmds[registered] = command; /* append default options, but not to --help */ if (!active) { for (i = 0; i < MAX_OPTIONS; i++) { if (!cmds[registered].options[i].name) { break; } } if (i > MAX_OPTIONS - countof(shared_options)) { fprintf(stderr, "command '%s' registered too many options, please " "increase MAX_OPTIONS\n", command.cmd); } else { for (j = 0; j < countof(shared_options); j++) { cmds[registered].options[i++] = shared_options[j]; } } for (i = 0; cmds[registered].line[i]; i++) { if (i == MAX_LINES - 1) { fprintf(stderr, "command '%s' specifies too many usage summary " "lines, please increase MAX_LINES\n", command.cmd); break; } } } registered++; } /** * Print usage text, with an optional error */ int command_usage(char *error) { FILE *out = stdout; int i; if (error) { out = stderr; } fprintf(out, "strongSwan %s PKI tool", VERSION); if (active == help_idx) { fprintf(out, "\n"); if (lib) { fprintf(out, "loaded plugins: %s\nusage:\n" " pki command [options]\ncommands:\n", lib->plugins->loaded_plugins(lib->plugins)); } for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) { fprintf(out, " --%-7s (-%c) %s\n", cmds[i].cmd, cmds[i].op, cmds[i].description); } fprintf(out, "options:\n"); for (i = 0; i < countof(shared_options); i++) { fprintf(out, " --%-7s (-%c) %s\n", shared_options[i].name, shared_options[i].op, shared_options[i].desc); } } else { fprintf(out, " (--%s/-%c)\n%s\nusage:\n", cmds[active].cmd, cmds[active].op, cmds[active].description); for (i = 0; i < MAX_LINES && cmds[active].line[i]; i++) { if (i == 0) { fprintf(out, " pki --%s %s\n", cmds[active].cmd, cmds[active].line[i]); } else { fprintf(out, " %s\n", cmds[active].line[i]); } } fprintf(out, "options:\n"); for (i = 0; i < MAX_OPTIONS && cmds[active].options[i].name; i++) { fprintf(out, " --%-15s (-%c) %s\n", cmds[active].options[i].name, cmds[active].options[i].op, cmds[active].options[i].desc); } } if (error) { fprintf(out, "error: %s\n", error); } return error != NULL; } /** * Show usage information */ static int help() { return command_usage(NULL); } /** * Dispatch cleanup hook */ static void cleanup() { options->destroy(options); } /* * Described in header */ int command_init(int c, char *v[]) { int op, i; argc = c; argv = v; options = options_create(); atexit(cleanup); active = help_idx = registered; command_register((command_t){help, 'h', "help", "show usage, version and " "plugin information"}); build_opts(); /* handle common options until we find a command */ if (!process_common_opts(TRUE)) { return command_usage("invalid --options"); } op = command_getopt(NULL); for (i = 0; i < MAX_COMMANDS && cmds[i].cmd; i++) { if (cmds[i].op == op) { active = i; build_opts(); /* handle common options again, now with specific options loaded */ if (!process_common_opts(FALSE)) { return command_usage("invalid --options"); } return 0; } } return command_usage(op != EOF ? "invalid command" : NULL); } /* * Described in header */ int command_dispatch() { return cmds[active].call(); }