From 6a3ab54c390bbeacf5b460c9910949f3dba8b0bd Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Mon, 15 Jul 2019 12:27:34 -0400 Subject: [PATCH 01/25] Initial checkin of V1.10.0 local dev branch - support for optargs - tab completions/help for optargs - 'buildmode' for optargs - add optarg to clitest - see changelog in spec file for more items --- Makefile | 4 +- clitest.c | 179 +++++- libcli.c | 1733 ++++++++++++++++++++++++++++++++++++++++++--------- libcli.h | 110 +++- libcli.spec | 17 +- 5 files changed, 1724 insertions(+), 319 deletions(-) diff --git a/Makefile b/Makefile index b082edf..f655222 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,8 @@ DESTDIR = PREFIX = /usr/local MAJOR = 1 -MINOR = 9 -REVISION = 8 +MINOR = 10 +REVISION = 0 LIB = libcli.so LIB_STATIC = libcli.a diff --git a/clitest.c b/clitest.c index 51cd760..e462283 100644 --- a/clitest.c +++ b/clitest.c @@ -1,5 +1,7 @@ #include #include +#include +#include #ifdef WIN32 #include #include @@ -174,22 +176,120 @@ void pc(UNUSED(struct cli_def *cli), const char *string) { printf("%s\n", string); } -int main() { - struct cli_command *c; - struct cli_def *cli; - int s, x; - struct sockaddr_in addr; - int on = 1; +#define MODE_POLYGON_TRIANGLE 20 +#define MODE_POLYGON_RECTANGLE 21 + +int cmd_perimeter( struct cli_def *cli, const char *command, char *argv[], int argc) { + struct cli_optarg_pair *optargs=cli_get_all_found_optargs(cli); + int i=1,numSides=0; + int perimeter=0; + char *shapeName=NULL; + + cli_print(cli, "perimeter callback, with %d args" , argc); + for (;optargs;optargs=optargs->next) + cli_print(cli, "%d, %s=%s", i++, optargs->name, optargs->value); + + shapeName = cli_get_optarg_value(cli, "shape", NULL); + if (!shapeName) { + cli_error(cli, "No shape name given"); + return CLI_ERROR; + } else if (strcmp(shapeName,"triangle") ==0 ) { + numSides=3; + } else if (strcmp(shapeName,"rectangle") == 0 ) { + numSides=4; + } else { + cli_error(cli, "Unrecognized shape given"); + return CLI_ERROR; + } + for (i=1;i<=numSides;i++) { + char sidename[50], *value; + int length; + snprintf(sidename,50,"side_%d",i); + value = cli_get_optarg_value(cli, sidename, NULL); + length = strtol(value, NULL,10); + perimeter += length; + } + cli_print(cli, "Perimeter is %d", perimeter); + return CLI_OK; +} -#ifndef WIN32 - signal(SIGCHLD, SIG_IGN); -#endif -#ifdef WIN32 - if (!winsock_init()) { - printf("Error initialising winsock\n"); - return 1; +const char *KnownShapes[] = {"rectangle","triangle", NULL}; + +int shape_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { + const char **shape ; + int rc=CLI_OK; + printf ("Calling shape_completor given %s" , word); + for (shape=KnownShapes; *shape && (rc==CLI_OK);shape++) { + if (!word || !strncmp(*shape, word, strlen(word))) { + rc=cli_add_comphelp_entry(comphelp, *shape); + } } -#endif + return rc; +} + +int shape_validator(struct cli_def *cli, const char *name, const char *value) { + const char **shape; + int rc=CLI_ERROR; + for (shape=KnownShapes; *shape; shape++) { + if (!strcmp(value, *shape)) return CLI_OK; + } + return rc; +} + +int shape_transient_eval( struct cli_def *cli, const char *name, const char *value) { + int rc=CLI_OK; + if ( !strcmp(value,"rectangle")) { + cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE); + rc=CLI_OK; + } else if (!strcmp(value, "triangle")) { + cli_set_transient_mode(cli, MODE_POLYGON_TRIANGLE); + rc=CLI_OK; + } else { + cli_error(cli, "unrecognized value for setting %s -> %s" , name, value); + rc=CLI_ERROR; + } + return rc; +} + +const char *KnownColors[] = {"black", "white","gray", "red","blue","green","lightred","lightblue","lightgreen", + "darkred","darkblue","darkgree","lavender", "yellow", NULL}; + +int color_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { + // Attempt to show matches against the following color strings + const char **color ; + int rc=CLI_OK; + for (color=KnownColors; *color && (rc==CLI_OK);color++) { + if (!word || !strncmp(*color, word, strlen(word))) { + rc=cli_add_comphelp_entry(comphelp, *color); + } + } + return rc; +} + +int color_validator(struct cli_def *cli, const char *name, const char *value) { + const char **color; + int rc=CLI_ERROR; + for (color=KnownColors; *color; color++) { + if (!strcmp(value, *color)) return CLI_OK; + } + return rc; +} + +int side_length_validator(struct cli_def *cli, const char *name, const char *value) { + // Verify 'value' is a positive number + long len; + char *endptr; + int rc = CLI_OK; + + errno=0; + len = strtol (value, &endptr, 10); + if ((endptr==value) || (*endptr != '\0') || ((errno==ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX))) )return CLI_ERROR; + return rc; +} + +void run_child(int x) { + struct cli_command *c; + struct cli_def *cli; // Prepare a small user context char mymessage[] = "I contain user data!"; @@ -203,7 +303,7 @@ int main() { cli_telnet_protocol(cli, 1); cli_regular(cli, regular_callback); cli_regular_interval(cli, 5); // Defaults to 1 second - cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second idle timeout +// cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second idle timeout cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); @@ -223,6 +323,32 @@ int main() { cli_register_command(cli, c, "regular", cmd_debug_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Enable cli_regular() callback debugging"); + // Register some commands/subcommands to demonstrate opt/arg and buildmode operations + + c=cli_register_command(cli, NULL, "perimeter", cmd_perimeter, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Calculate perimeter of polygon"); + cli_register_optarg(c, "transparent", CLI_CMD_OPTIONAL_FLAG, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", + NULL, NULL, NULL); + cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT|CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape to calclate perimeter for", + shape_completor, shape_validator, shape_transient_eval); + cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", + color_completor, color_validator, NULL); + cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 1 length", + NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 1 length", + NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 2 length", + NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 2 length", + NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 3 length", + NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 3 length", + NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_4", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 4 length", + NULL, side_length_validator, NULL); + + + // Set user context and its command cli_set_context(cli, (void *)&myctx); cli_register_command(cli, NULL, "context", cmd_context, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, @@ -242,6 +368,24 @@ int main() { fclose(fh); } } + cli_loop(cli,x); + cli_done(cli); +} + +int main() { + int s, x; + struct sockaddr_in addr; + int on = 1; + +#ifndef WIN32 + signal(SIGCHLD, SIG_IGN); +#endif +#ifdef WIN32 + if (!winsock_init()) { + printf("Error initialising winsock\n"); + return 1; + } +#endif if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); @@ -287,15 +431,14 @@ int main() { /* child */ close(s); - cli_loop(cli, x); + run_child(x); exit(0); #else - cli_loop(cli, x); + run_child(x); shutdown(x, SD_BOTH); close(x); #endif } - cli_done(cli); return 0; } diff --git a/libcli.c b/libcli.c index 0b4d50b..bc00348 100644 --- a/libcli.c +++ b/libcli.c @@ -127,23 +127,41 @@ struct cli_filter_cmds { } \ } while (0) -int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); -int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); -int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); -int cli_match_filter(struct cli_def *cli, const char *string, void *data); -int cli_range_filter(struct cli_def *cli, const char *string, void *data); -int cli_count_filter(struct cli_def *cli, const char *string, void *data); - -static struct cli_filter_cmds filter_cmds[] = { - {"begin", "Begin with lines that match"}, - {"between", "Between lines that match"}, - {"count", "Count of lines"}, - {"exclude", "Exclude lines that match"}, - {"include", "Include lines that match"}, - {"grep", "Include lines that match regex (options: -v, -i, -e)"}, - {"egrep", "Include lines that match extended regex"}, - {NULL, NULL}, -}; +// Forward defines of *INTERNAL* library function as static here +static int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); +static int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); +static int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); +static int cli_match_filter(struct cli_def *cli, const char *string, void *data); +static int cli_range_filter(struct cli_def *cli, const char *string, void *data); +static int cli_count_filter(struct cli_def *cli, const char *string, void *data); +static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp); +static int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text); +static char * cli_int_buildmode_extend_cmdline(char *, char *word); +static void cli_int_free_buildmode(struct cli_def *cli); +static int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); +static void cli_free_command(struct cli_command *cmd); +static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)) ; +static struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, + int (*callback)(struct cli_def *cli, const char *, char **, int), + int privilege, int mode, const char *help) ; +static int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; +static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) ; +static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) ; +static int cli_int_execute_buildmode(struct cli_def *cli); +static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) ; +//static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name ) ; +static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command); +static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) ; +static int cli_int_execute_pipeline (struct cli_def *cli, struct cli_pipeline *pipeline); +inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); +static struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline); + static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; @@ -300,6 +318,7 @@ int cli_set_privilege(struct cli_def *cli, int priv) { if (priv != old) { cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? "# " : "> "); cli_build_shortest(cli, cli->commands); + if (cli->filter_commands) cli_build_shortest(cli, cli->filter_commands); } return old; @@ -327,6 +346,7 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) { } cli_build_shortest(cli, cli->commands); + cli_build_shortest(cli, cli->filter_commands); } return old; @@ -387,6 +407,7 @@ static void cli_free_command(struct cli_command *cmd) { free(cmd->command); if (cmd->help) free(cmd->help); + if (cmd->optargs) cli_unregister_all_optarg(cmd->optargs); free(cmd); } @@ -521,6 +542,18 @@ struct cli_def *cli_init() { cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Configure from the terminal"); + + // and now the built in filters + c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); + + c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Between lines that match"); + + c = cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Count of lines"); + c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exclude lines that match"); + c = cli_register_filter(cli, "include", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match"); + c = cli_register_filter(cli, "grep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match regex (options: -v, -i, -e)"); + c = cli_register_filter(cli, "egrep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match extended regex"); + cli->privilege = cli->mode = -1; cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, 0); @@ -545,7 +578,7 @@ void cli_unregister_all(struct cli_def *cli, struct cli_command *command) { // Unregister all child commands if (c->children) cli_unregister_all(cli, c->children); - + if (c->optargs) cli_unregister_all_optarg(c->optargs); if (c->command) free(c->command); if (c->help) free(c->help); free(c); @@ -569,9 +602,11 @@ int cli_done(struct cli_def *cli) { u = n; } - /* free all commands */ - cli_unregister_all(cli, 0); + /* free all commands and filters */ + cli_unregister_all(cli, cli->commands); + cli_unregister_all(cli, cli->filter_commands); + if ( cli->buildmode) cli_int_free_buildmode(cli); free_z(cli->commandname); free_z(cli->modestring); free_z(cli->banner); @@ -676,267 +711,111 @@ static char *join_words(int argc, char **argv) { return p; } -static int cli_find_command(struct cli_def *cli, struct cli_command *commands, int num_words, char *words[], - int start_word, int filters[]) { - struct cli_command *c, *again_config = NULL, *again_any = NULL; - int c_words = num_words; - - if (filters[0]) c_words = filters[0]; - - // Deal with ? for help - if (!words[start_word]) return CLI_ERROR; - - if (words[start_word][strlen(words[start_word]) - 1] == '?') { - int l = strlen(words[start_word]) - 1; - - if (commands->parent && commands->parent->callback) - cli_error(cli, "%-20s %s", cli_command_name(cli, commands->parent), - (commands->parent->help != NULL ? commands->parent->help : "")); - - for (c = commands; c; c = c->next) { - if (strncasecmp(c->command, words[start_word], l) == 0 && (c->callback || c->children) && - cli->privilege >= c->privilege && (c->mode == cli->mode || c->mode == MODE_ANY)) - cli_error(cli, " %-20s %s", c->command, (c->help != NULL ? c->help : "")); - } - - return CLI_OK; - } - - for (c = commands; c; c = c->next) { - if (cli->privilege < c->privilege) continue; - - if (strncasecmp(c->command, words[start_word], c->unique_len)) continue; - - if (strncasecmp(c->command, words[start_word], strlen(words[start_word]))) continue; - - AGAIN: - if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL)) { - int rc = CLI_OK; - int f; - struct cli_filter **filt = &cli->filters; - - // Found a word! - if (!c->children) { - // Last word - if (!c->callback) { - cli_error(cli, "No callback for \"%s\"", cli_command_name(cli, c)); - return CLI_ERROR; - } - } else { - if (start_word == c_words - 1) { - if (c->callback) goto CORRECT_CHECKS; - - cli_error(cli, "Incomplete command"); - return CLI_ERROR; - } - rc = cli_find_command(cli, c->children, num_words, words, start_word + 1, filters); - if (rc == CLI_ERROR_ARG) { - if (c->callback) { - rc = CLI_OK; - goto CORRECT_CHECKS; - } else { - cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", words[start_word]); - } - } - return rc; - } - - if (!c->callback) { - cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c)); - return CLI_ERROR; - } - - CORRECT_CHECKS: - for (f = 0; rc == CLI_OK && filters[f]; f++) { - int n = num_words; - char **argv; - int argc; - int len; - - if (filters[f + 1]) n = filters[f + 1]; - - if (filters[f] == n - 1) { - cli_error(cli, "Missing filter"); - return CLI_ERROR; - } - - argv = words + filters[f] + 1; - argc = n - (filters[f] + 1); - len = strlen(argv[0]); - if (argv[argc - 1][strlen(argv[argc - 1]) - 1] == '?') { - if (argc == 1) { - int i; - for (i = 0; filter_cmds[i].cmd; i++) cli_error(cli, " %-20s %s", filter_cmds[i].cmd, filter_cmds[i].help); - } else { - if (argv[0][0] != 'c') // count - cli_error(cli, " WORD"); - - if (argc > 2 || argv[0][0] == 'c') // count - cli_error(cli, " "); - } - - return CLI_OK; - } - - if (argv[0][0] == 'b' && len < 3) // [beg]in, [bet]ween - { - cli_error(cli, "Ambiguous filter \"%s\" (begin, between)", argv[0]); - return CLI_ERROR; - } - *filt = calloc(sizeof(struct cli_filter), 1); - - if (!strncmp("include", argv[0], len) || !strncmp("exclude", argv[0], len) || !strncmp("grep", argv[0], len) || - !strncmp("egrep", argv[0], len)) - rc = cli_match_filter_init(cli, argc, argv, *filt); - else if (!strncmp("begin", argv[0], len) || !strncmp("between", argv[0], len)) - rc = cli_range_filter_init(cli, argc, argv, *filt); - else if (!strncmp("count", argv[0], len)) - rc = cli_count_filter_init(cli, argc, argv, *filt); - else { - cli_error(cli, "Invalid filter \"%s\"", argv[0]); - rc = CLI_ERROR; - } - - if (rc == CLI_OK) { - filt = &(*filt)->next; - } else { - free(*filt); - *filt = 0; - } - } - - if (rc == CLI_OK) - rc = c->callback(cli, cli_command_name(cli, c), words + start_word + 1, c_words - start_word - 1); - - while (cli->filters) { - struct cli_filter *filt = cli->filters; - - // call one last time to clean up - filt->filter(cli, NULL, filt->data); - cli->filters = filt->next; - free(filt); - } - - return rc; - } else if (cli->mode > MODE_CONFIG && c->mode == MODE_CONFIG) { - // command matched but from another mode, - // remember it if we fail to find correct command - again_config = c; - } else if (c->mode == MODE_ANY) { - // command matched but for any mode, - // remember it if we fail to find correct command - again_any = c; - } - } - - // drop out of config submode if we have matched command on MODE_CONFIG - if (again_config) { - c = again_config; - cli_set_configmode(cli, MODE_CONFIG, NULL); - goto AGAIN; - } - if (again_any) { - c = again_any; - goto AGAIN; - } - - if (start_word == 0) - cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", words[start_word]); - - return CLI_ERROR_ARG; -} int cli_run_command(struct cli_def *cli, const char *command) { - int r; - unsigned int num_words, i, f; - char *words[CLI_MAX_LINE_WORDS] = {0}; - int filters[CLI_MAX_LINE_WORDS] = {0}; - - if (!command) return CLI_ERROR; - while (isspace(*command)) command++; - - if (!*command) return CLI_OK; - - num_words = cli_parse_line(command, words, CLI_MAX_LINE_WORDS); - for (i = f = 0; i < num_words && f < CLI_MAX_LINE_WORDS - 1; i++) { - if (words[i][0] == '|') filters[f++] = i; - } - - filters[f] = 0; + int rc=CLI_ERROR; + struct cli_pipeline *pipeline ; - if (num_words) - r = cli_find_command(cli, cli->commands, num_words, words, 0, filters); - else - r = CLI_ERROR; - - for (i = 0; i < num_words; i++) free(words[i]); - - if (r == CLI_QUIT) return r; - - return CLI_OK; -} - -static int cli_get_completions(struct cli_def *cli, const char *command, char **completions, int max_completions) { - struct cli_command *c; - struct cli_command *n; - int num_words, save_words, i, k = 0; - char *words[CLI_MAX_LINE_WORDS] = {0}; - int filter = 0; + // split command into pipeline stages, - if (!command) return 0; - while (isspace(*command)) command++; + pipeline=cli_int_generate_pipeline(cli, command); - save_words = num_words = cli_parse_line(command, words, sizeof(words) / sizeof(words[0])); - if (!command[0] || command[strlen(command) - 1] == ' ') num_words++; + // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_COMMAND_START if found. + if (pipeline) rc=cli_int_validate_pipeline(cli, pipeline); + - if (!num_words) goto out; - - for (i = 0; i < num_words; i++) { - if (words[i] && words[i][0] == '|') filter = i; + if (rc == CLI_OK) { + rc = cli_int_execute_pipeline(cli, pipeline); } + cli_int_free_pipeline(pipeline); + return rc; +} - if (filter) // complete filters - { - unsigned len = 0; - - if (filter < num_words - 1) // filter already completed - goto out; - if (filter == num_words - 1) len = strlen(words[num_words - 1]); - for (i = 0; filter_cmds[i].cmd && k < max_completions; i++) { - if (!len || (len < strlen(filter_cmds[i].cmd) && !strncmp(filter_cmds[i].cmd, words[num_words - 1], len))) - completions[k++] = (char *)filter_cmds[i].cmd; - } +void cli_get_completions(struct cli_def *cli, const char *command, char lastchar, struct cli_comphelp *comphelp) { + struct cli_command *c=NULL; + struct cli_command *n=NULL; + struct cli_command *first_command=NULL; - completions[k] = NULL; - goto out; + int i; + struct cli_pipeline *pipeline=NULL ; + struct cli_pipeline_stage *stage; + + if (!(pipeline=cli_int_generate_pipeline(cli, command))) goto out; + + stage=&pipeline->stage[pipeline->num_stages-1]; + + // check to see if either *no* input, or if the lastchar is a tab. + if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words-1])) stage->num_words++; + + if (cli->buildmode && cli->buildmode->commands) { + first_command=cli->buildmode->commands; + } else if (pipeline->num_stages==1) { + first_command=cli->commands; + } else { + first_command=cli->filter_commands; } - - for (c = cli->commands, i = 0; c && i < num_words && k < max_completions; c = n) { + + for (c = first_command, i = 0; c && i < stage->num_words ; c = n) { + char *strptr=NULL; n = c->next; - + if (cli->privilege < c->privilege) continue; - if (c->mode != cli->mode && c->mode != MODE_ANY) continue; + if (c->mode != cli->mode && c->mode != MODE_ANY ) continue; - if (words[i] && strncasecmp(c->command, words[i], strlen(words[i]))) continue; + if (stage->words[i] && strncasecmp(c->command, stage->words[i], strlen(stage->words[i]))) continue; - if (i < num_words - 1) { - if (strlen(words[i]) < c->unique_len) continue; + // special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set + if (cli->buildmode) { + struct cli_optarg *optarg; + for (optarg = cli->buildmode->c->optargs; optarg; optarg=optarg->next) { + if (!strcmp(optarg->name, c->command)) break; + } + if (optarg && cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) continue; + } + if (i < stage->num_words-1) { + if (stage->words[i] && (strlen(stage->words[i]) < c->unique_len) && strcmp(stage->words[i], c->command)) continue; n = c->children; + + // if we have no more children, we've matched the *command* - remember this + if (!c->children) { + break; + } + i++; continue; } - - completions[k++] = c->command; + + if ((lastchar == '?')) { + if (asprintf(&strptr," %-20s %s", c->command, (c->help)?c->help : "") != -1 ) { + cli_add_comphelp_entry(comphelp, strptr); + free_z(strptr); + } + } else { + cli_add_comphelp_entry(comphelp, c->command); + } + } out: - for (i = 0; i < save_words; i++) free(words[i]); + if (c) { + // advance past first word of stage + i++; + stage->first_unmatched=i; + if (c->optargs ) { + + cli_int_parse_optargs(cli, stage, c, lastchar, comphelp); + } else if (lastchar == '?') { + // special case for getting help with no defined optargs.... + comphelp->num_entries = -1; + } + } + + pipeline=cli_int_free_pipeline(pipeline); - return k; } static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) { @@ -1002,17 +881,24 @@ static int show_prompt(struct cli_def *cli, int sockfd) { if (cli->hostname) len += write(sockfd, cli->hostname, strlen(cli->hostname)); if (cli->modestring) len += write(sockfd, cli->modestring, strlen(cli->modestring)); - + if (cli->buildmode) { + len += write(sockfd, "[", 1); + len += write(sockfd, cli->buildmode->cname, strlen(cli->buildmode->cname)); + len += write(sockfd, "...", 3); + if (cli->buildmode->mode_text) len += write(sockfd, cli->buildmode->mode_text, strlen(cli->buildmode->mode_text)); + len+= write(sockfd, "]", 1); + } return len + write(sockfd, cli->promptchar, strlen(cli->promptchar)); } + int cli_loop(struct cli_def *cli, int sockfd) { - unsigned char c; int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0; char *cmd = NULL, *oldcmd = 0; char *username = NULL, *password = NULL; cli_build_shortest(cli, cli->commands); + cli_build_shortest(cli, cli->filter_commands); cli->state = STATE_LOGIN; cli_free_history(cli); @@ -1055,7 +941,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { while (1) { signed int in_history = 0; - int lastchar = 0; + unsigned char lastchar = '\0'; + unsigned char c = '\0'; struct timeval tm; cli->showprompt = 1; @@ -1077,6 +964,13 @@ int cli_loop(struct cli_def *cli, int sockfd) { while (1) { int sr; fd_set r; + + /* + * ensure our transient mode is reset to the starting mode on *each* loop traversal + * transient mode is valid only while a command is being evaluated/executed + */ + cli->transient_mode = cli->mode; + if (cli->showprompt) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2); @@ -1272,8 +1166,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { } else { int i; if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { - // back up one space, then write current buffer followed by a - // space + // back up one space, then write current buffer followed by a space _write(sockfd, "\b", 1); _write(sockfd, cmd + cursor, l - cursor); _write(sockfd, " ", 1); @@ -1328,10 +1221,10 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (cursor == l) continue; if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { - int c; - for (c = cursor; c < l; c++) _write(sockfd, " ", 1); + int cptr; + for (cptr = cursor; cptr < l; cptr++) _write(sockfd, " ", 1); - for (c = cursor; c < l; c++) _write(sockfd, "\b", 1); + for (cptr = cursor; cptr < l; cptr++) _write(sockfd, "\b", 1); } memset(cmd + cursor, 0, l - cursor); @@ -1352,8 +1245,10 @@ int cli_loop(struct cli_def *cli, int sockfd) { /* disable */ if (c == CTRL('Z')) { if (cli->mode != MODE_EXEC) { + if (cli->buildmode) cli_int_free_buildmode(cli); cli_clear_line(sockfd, cmd, l, cursor); cli_set_configmode(cli, MODE_EXEC, NULL); + l = cursor = 0; cli->showprompt = 1; } @@ -1362,49 +1257,131 @@ int cli_loop(struct cli_def *cli, int sockfd) { /* TAB completion */ if (c == CTRL('I')) { - char *completions[CLI_MAX_LINE_WORDS]; - int num_completions = 0; + struct cli_comphelp comphelp = {0}; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; if (cursor != l) continue; - num_completions = cli_get_completions(cli, cmd, completions, CLI_MAX_LINE_WORDS); - if (num_completions == 0) { + cli_get_completions(cli, cmd, c, &comphelp); + if (comphelp.num_entries == 0) { _write(sockfd, "\a", 1); - } else if (num_completions == 1) { - // Single completion - for (; l > 0; l--, cursor--) { - if (cmd[l - 1] == ' ' || cmd[l - 1] == '|') break; - _write(sockfd, "\b", 1); - } - strcpy((cmd + l), completions[0]); - l += strlen(completions[0]); - cmd[l++] = ' '; - cursor = l; - _write(sockfd, completions[0], strlen(completions[0])); - _write(sockfd, " ", 1); } else if (lastchar == CTRL('I')) { // double tab int i; - _write(sockfd, "\r\n", 2); - for (i = 0; i < num_completions; i++) { - _write(sockfd, completions[i], strlen(completions[i])); - if (i % 4 == 3) + for (i = 0; i < comphelp.num_entries; i++) { + if (i % 4 == 0 ) _write(sockfd, "\r\n", 2); else - _write(sockfd, " ", 1); + _write(sockfd, " ", 1); + _write(sockfd, comphelp.entries[i], strlen(comphelp.entries[i])); } - if (i % 4 != 3) _write(sockfd, "\r\n", 2); + _write(sockfd, "\r\n", 2); cli->showprompt = 1; - } else { - // More than one completion + } else if (comphelp.num_entries == 1) { + // Single completion - show *unless* the optional/required 'prefix' is present + if ((comphelp.entries[0][0]!='[') && (comphelp.entries[0][0]!='<')) { + for (; l > 0; l--, cursor--) { + if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l-1]==',')) break; + _write(sockfd, "\b", 1); + } + strcpy((cmd + l), comphelp.entries[0]); + l += strlen(comphelp.entries[0]); + cmd[l++] = ' '; + cursor = l; + _write(sockfd, comphelp.entries[0], strlen(comphelp.entries[0])); + _write(sockfd, " ", 1); + // and now forget the tab, since we just found a single match + lastchar = '\0'; + } else { + // Yes, we had a match, but it wasn't required - remember the tab in case the user double tabs.... + lastchar = CTRL('I'); + } + } else if (comphelp.num_entries>1) { + /* More than one completion + * Show as many characters as we can until the completions start to differ + */ lastchar = c; - _write(sockfd, "\a", 1); + int i,j,k=0; + char *tptr = comphelp.entries[0]; + + /* quickly try to see where our entries differ + * corner cases + * - if all entries are optional, don't show + * *any* options unless user has provided a letter. + * - if any entry starts with '<' then don't fill in + * anything. + */ + + // skip a leading '[' + k = strlen(tptr); + if (*tptr == '[') tptr++; + else if (*tptr == '<') k=0; + + for (i = 1; k!=0 && i < comphelp.num_entries; i++) { + char *wptr=comphelp.entries[i]; + + if (*wptr=='[') wptr++; + else if (*wptr=='<') k=0; + + for (j=0; (j 0; l--, cursor--) { + if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l-1]==',')) break; + _write(sockfd, "\b", 1); + } + strncpy((cmd + l), tptr,k); + l += k; + cursor = l; + _write(sockfd, tptr, k); + + } else { + _write(sockfd, "\a", 1); + } } + cli_free_comphelp(&comphelp); continue; } + /* '?' at end of line - generate applicable 'help' messages */ + if ((c == '?') && (cursor == l) ) { + struct cli_comphelp comphelp = {0}; + int i; + int show_cr=1; + + if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; + + if (cursor != l) continue; + + cli_get_completions(cli, cmd, c, &comphelp); + if (comphelp.num_entries == 0) { + _write(sockfd, "\a", 1); + } + else if (comphelp.num_entries > 0) { + cli->showprompt = 1; + _write(sockfd, "\r\n", 2); + for (i=0;i<(int)comphelp.num_entries;i++) { + if (comphelp.entries[i][2]!='[') show_cr=0; + cli_error(cli, "%s",comphelp.entries[i]); + } + if (show_cr) cli_error(cli," "); + } + + cli_free_comphelp(&comphelp); + + if ( comphelp.num_entries>=0) continue; + } + /* history */ if (c == CTRL('P') || c == CTRL('N')) { int history_found = 0; @@ -1612,10 +1589,37 @@ int cli_loop(struct cli_def *cli, int sockfd) { cli->state = STATE_NORMAL; } } else { + int rc; if (l == 0) continue; if (cmd[l - 1] != '?' && strcasecmp(cmd, "history") != 0) cli_add_history(cli, cmd); - if (cli_run_command(cli, cmd) == CLI_QUIT) break; + rc=cli_run_command(cli, cmd); + switch (rc) { + case CLI_BUILDMODE_COMMAND_ERROR: + // unable to enter buildmode successfully + cli_print(cli, "Failure entering build mode for '%s'" , cli->buildmode->cname); + cli_int_free_buildmode(cli); + continue; + case CLI_BUILDMODE_COMMAND_CANCEL: + // called if user enters 'cancel' + cli_print(cli, "Canceling build mode for '%s'" , cli->buildmode->cname); + cli_int_free_buildmode(cli); + break; + case CLI_BUILDMODE_COMMAND_EXIT: + // called when user enters exit - rebuild *entire* command line + // recall all located optargs + cli->found_optargs = cli->buildmode->found_optargs; + rc = cli_int_execute_buildmode(cli); + case CLI_QUIT: + break; + case CLI_BUILDMODE_COMMAND_START: + case CLI_BUILDMODE_COMMAND_EXTEND: + default: + break; + } + + // Process is done if we get a CLI_QUIT, + if (rc==CLI_QUIT) break; } // Update the last_action time now as the last command run could take a @@ -1760,7 +1764,8 @@ int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli filt->filter = cli_match_filter; filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1); - + if (!state) return CLI_ERROR; + if (argv[0][0] == 'i' || (argv[0][0] == 'e' && argv[0][1] == 'x')) // include/exclude { if (argv[0][0] == 'e') state->flags = MATCH_INVERT; @@ -1887,11 +1892,15 @@ int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli filt->filter = cli_range_filter; filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1); - - state->from = from; - state->to = to; - - return CLI_OK; + if (state) { + state->from = from; + state->to = to; + return CLI_OK; + } else { + free_z(from); + free_z(to); + return CLI_ERROR; + } } int cli_range_filter(UNUSED(struct cli_def *cli), const char *string, void *data) { @@ -1974,3 +1983,1137 @@ void cli_set_context(struct cli_def *cli, void *context) { void *cli_get_context(struct cli_def *cli) { return cli->user_context; } + + +/* cli_register_filter/cli_unregister_filter are limited coies of cli_register_command/cli_unregister_command. + * Filters do not have a hierarchy, so they are all siblings of each other so we only need the 'next' field. + */ +struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, + int(*init) (struct cli_def *cli, int, char **, struct cli_filter *), + int(*filter)(struct cli_def *, const char *, void *), + int privilege, int mode, const char *help) { + struct cli_command *c, *p; + + if (!command) return NULL; + if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; + + c->init = init; + c->filter = filter; + c->next = NULL; + if (!(c->command = strdup(command))) { + free(c); + return NULL; + } + + c->privilege = privilege; + c->mode = mode; + if (help && !(c->help = strdup(help))) { + free(c->command); + free(c); + return NULL; + } + + if (!cli->filter_commands) { + cli->filter_commands = c; + } else { + for (p = cli->filter_commands; p && p->next; p = p->next) + ; + if (p) p->next = c; + } + return c; +} + + +int cli_unregister_filter(struct cli_def *cli, const char *command) { + struct cli_command *c, *p = NULL; + + if (!command) return -1; + if (!cli->filter_commands) return CLI_OK; + + for (c = cli->filter_commands; c; c = c->next) { + if (strcmp(c->command, command) == 0) { + if (p) + p->next = c->next; + else + cli->filter_commands = c->next; + cli_free_command(c); + return CLI_OK; + } + p = c; + } + return CLI_OK; +} + + +void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) { + struct cli_optarg_pair *c; + + if (!optarg_pair || !*optarg_pair) return ; + + for (c=*optarg_pair;c;) { + *optarg_pair=c->next; + free_z(c->name); + free_z(c->value); + free_z(c); + c=*optarg_pair; + } +} + +char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) { + char *value=NULL; + struct cli_optarg_pair *optarg_pair; + if (!name || !cli->found_optargs) return NULL; + + for (optarg_pair = cli->found_optargs; optarg_pair && !value ; optarg_pair=optarg_pair->next) { + if (strcmp(optarg_pair->name, name)==0) { + if (find_after && (find_after==optarg_pair->value)) { + find_after=NULL; + continue; + } + value = optarg_pair->value; + } + } + return value; +} + +static void cli_optarg_build_shortest(struct cli_optarg *optarg) { + struct cli_optarg *c, *p; + char *cp, *pp; + unsigned int len; + + for (c = optarg; c; c = c->next) { + c->unique_len = 1; + for (p=optarg; p; p=p->next) { + if (c==p) continue; + cp = c->name; + pp = p->name; + len=1; + while (*cp && *pp && *cp++ == *pp++) len++; + if (len > c->unique_len) c->unique_len = len; + } + } +} + +void cli_free_optarg(struct cli_optarg *optarg) { + free_z(optarg->help); + free_z(optarg->name); + free_z(optarg); +} + + +int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int privilege, int mode, const char *help, + int (*get_completions)(struct cli_def *cli, const char *, const char *, struct cli_comphelp * ), + int (*validator)(struct cli_def *cli, const char *, const char *), + int (*transient_mode)(struct cli_def *cli, const char *, const char *)) { + struct cli_optarg *optarg; + struct cli_optarg *lastopt=NULL; + struct cli_optarg *ptr=NULL; + int retval = CLI_ERROR; + + // name must not already exist with this priv/mode + for (ptr=cmd->optargs,lastopt=NULL; ptr ;lastopt=ptr, ptr=ptr->next) { + if (!(strcmp(name, ptr->name)) && (ptr->mode == mode) && (ptr->privilege == privilege)) { + return CLI_ERROR; + } + } + if (!(optarg = calloc(sizeof(struct cli_optarg),1))) goto CLEANUP; + if (!(optarg->name = strdup(name))) goto CLEANUP; + if (help && !(optarg->help = strdup(help))) goto CLEANUP; + + optarg->mode = mode; + optarg->privilege = privilege; + optarg->get_completions = get_completions; + optarg->validator = validator; + optarg->transient_mode = transient_mode; + optarg->flags = flags; + + if (lastopt) lastopt->next = optarg; + else cmd->optargs = optarg; + cli_optarg_build_shortest(cmd->optargs); + retval = CLI_OK; + +CLEANUP: + if (retval != CLI_OK) { + cli_free_optarg(optarg); + } + return retval; +} + +int cli_unregister_optarg(struct cli_command *cmd, const char *name) { + struct cli_optarg *ptr; + struct cli_optarg *lastptr; + int retval = CLI_ERROR; + // iterate looking for this option name, stoping at end or if name matches + for (lastptr=NULL, ptr=cmd->optargs; ptr && strcmp(ptr->name, name); lastptr=ptr, ptr=ptr->next) + { + ; + } + + // if ptr, then we found the optarg to delete + if (ptr) { + if (lastptr) { + // not first optarg + lastptr->next = ptr->next; + ptr->next=NULL; + } + else { + // first optarg + cmd->optargs = ptr->next; + ptr->next=NULL; + } + cli_free_optarg(ptr); + cli_optarg_build_shortest(cmd->optargs); + retval = CLI_OK; + } + return retval; +} + +void cli_unregister_all_optarg(struct cli_optarg *optarg) { + struct cli_optarg *p; + + for (;optarg; optarg=p) { + p=optarg->next; + cli_free_optarg(optarg); + } +} + +void cli_int_unset_optarg_value(struct cli_def *cli, const char *name) { + struct cli_optarg_pair **p,*c; + for (p=&cli->found_optargs, c=*p ; *p; ) { + c=*p; + + if (!strcmp(c->name, name)) { + *p = c->next; + free_z(c->name); + free_z(c->value); + free_z(c); + } else { + p = &(*p)->next; + } + } +} + +int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple) { + struct cli_optarg_pair *optarg_pair, **anchor; + int rc = CLI_ERROR; + + for (optarg_pair=cli->found_optargs, anchor=&cli->found_optargs; optarg_pair ; anchor = &optarg_pair->next, optarg_pair=optarg_pair->next) { + // break if we found this name *and* allow_multiple is false + if (!strcmp(optarg_pair->name, name) && !allow_multiple) { + break; + } + } + // if we *didn't* find this, then allocate a new entry before proceeding + if (!optarg_pair) { + optarg_pair = (struct cli_optarg_pair*)calloc(1,sizeof(struct cli_optarg_pair)); + *anchor = optarg_pair; + } + // set the value + if (optarg_pair) { + // name is null only if we didn't find it + if (!optarg_pair->name) optarg_pair->name=strdup(name); + + // value may be overwritten, so free any old value. + if ( optarg_pair->value) free_z(optarg_pair->value); + optarg_pair->value=strdup(value); + + rc=CLI_OK; + } + return rc; +} + +struct cli_optarg_pair * cli_get_all_found_optargs(struct cli_def *cli) { + if (cli ) return cli->found_optargs; + return NULL; +} + + +char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after) { + char *value=NULL; + struct cli_optarg_pair *optarg_pair; + + for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair=optarg_pair->next) { + if (strcasecmp(optarg_pair->name, name)) continue; + if (find_after && (optarg_pair->value == find_after)) { + find_after=NULL; + continue; + } + value = optarg_pair->value; + } + return value; +} + + + +void cli_int_free_buildmode(struct cli_def *cli) { + if (!cli || !cli->buildmode) return; + if (cli->buildmode->commands) cli_unregister_all(cli, cli->buildmode->commands); + cli->mode = cli->buildmode->mode; + free_z(cli->buildmode->cname); + free_z(cli->buildmode->mode_text); + cli_int_free_found_optargs(&cli->buildmode->found_optargs); + free_z(cli->buildmode); +} + +int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text) { + struct cli_optarg *optarg; + struct cli_command *c; + struct cli_buildmode *buildmode ; + + + if ( (!cli ) || !(buildmode=(struct cli_buildmode *)calloc(1,sizeof(struct cli_buildmode)))) { + cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command); + return CLI_BUILDMODE_COMMAND_ERROR; + } + + // clean up any shrapnel from earlier - shouldn't be any but.... + if ( cli->buildmode) { + cli_int_free_buildmode(cli); + } + + // assign it so cli_int_register_buildmode_command() has something to work with + cli->buildmode = buildmode; + cli->buildmode->mode = cli->mode; + cli->buildmode->transient_mode = cli->transient_mode; + if (mode_text) cli->buildmode->mode_text = strdup(mode_text); + + // need this to verify we have all *required* arguments + cli->buildmode->c = stage->command; + + // build new *limited* list of commands from this commands optargs + for (optarg = stage->command->optargs; optarg ; optarg=optarg->next) { + // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel + if (!strcmp(optarg->name,"cancel") || (!strcmp(optarg->name, "exit"))) { + cli_error(cli, "Unable to build buildmode mode from optarg named %s", optarg->name); + return CLI_BUILDMODE_COMMAND_ERROR; + } + if (optarg->flags&(CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE)) continue; + if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) continue; + else if (optarg->flags & ( CLI_CMD_OPTIONAL_ARGUMENT|CLI_CMD_ARGUMENT)) { + if ((c=cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_cmd_cback, optarg->privilege, cli->mode, optarg->help))) { + cli_register_optarg(c, optarg->name, CLI_CMD_ARGUMENT|(optarg->flags&CLI_CMD_OPTION_MULTIPLE), optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, NULL); + } + else { + return CLI_BUILDMODE_COMMAND_ERROR; + } + } else { + if (optarg->flags&CLI_CMD_OPTION_MULTIPLE) { + if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, optarg->privilege, cli->mode, optarg->help)) { + return CLI_BUILDMODE_COMMAND_ERROR; + } + } else { + if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->privilege, cli->mode, optarg->help)) { + return CLI_BUILDMODE_COMMAND_ERROR; + } + } + } + } + cli->buildmode->cname = strdup(cli_command_name(cli, stage->command)); + // and lastly two 'always there' commands to cancel current mode and to execute the command + cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); + cli_int_register_buildmode_command(cli, NULL, "exit", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Exit and execute command"); + cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Show current settings"); + c=cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Unset a setting"); + cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT|CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode, "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); + + cli_build_shortest(cli, cli->buildmode->commands); + + return CLI_BUILDMODE_COMMAND_START; +} + +int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) { + struct cli_command *c, *p = NULL; + + if (!command) return -1; + if (!cli->buildmode || !cli->buildmode->commands) return CLI_OK; + + for (c = cli->buildmode->commands; c; c = c->next) { + if (strcmp(c->command, command) == 0) { + if (p) + p->next = c->next; + else + cli->buildmode->commands = c->next; + + cli_free_command(c); + return CLI_OK; + } + p = c; + } + + return CLI_OK; +} + +// a copy of cli_register_command, but using cli->buildmode_cmd rather than cli->commands as the anchor + +struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, + int (*callback)(struct cli_def *cli, const char *, char **, int), + int privilege, int mode, const char *help) { + struct cli_command *c, *p; + + if (!command) return NULL; + if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; + + c->callback = callback; + c->next = NULL; + if (!(c->command = strdup(command))) { + free(c); + return NULL; + } + + c->parent = parent; + c->privilege = privilege; + c->mode = mode; + if (help && !(c->help = strdup(help))) { + free(c->command); + free(c); + return NULL; + } + + if (parent) { + if (!parent->children) { + parent->children = c; + } else { + for (p = parent->children; p && p->next; p = p->next) + ; + if (p) p->next = c; + } + } else { + if (!cli->buildmode->commands) { + cli->buildmode->commands = c; + } else { + for (p = cli->buildmode->commands; p && p->next; p = p->next) + ; + if (p) p->next = c; + } + } + return c; +} + +int cli_int_execute_buildmode(struct cli_def *cli) { + struct cli_optarg *optarg=NULL; + int rc = CLI_OK; + char *cmdline; + + char *value=NULL; + + cmdline = strdup(cli_command_name(cli,cli->buildmode->c)); + for (optarg=cli->buildmode->c->optargs; rc==CLI_OK && optarg;optarg=optarg->next) { + value=NULL; + do { + if (cli->privilege < optarg->privilege) continue; + if ((optarg->mode!=cli->buildmode->mode) && (optarg->mode != cli->buildmode->transient_mode) && (optarg->mode != MODE_ANY)) continue; + + value = cli_get_optarg_value(cli,optarg->name,value); + if (!value && optarg->flags & CLI_CMD_ARGUMENT) { + cli_error(cli,"Missing required argument %s", optarg->name); + rc=CLI_MISSING_ARGUMENT; + } + else if (value) { + if (optarg->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) { + if (!(cmdline=cli_int_buildmode_extend_cmdline(cmdline,value))) { + cli_error(cli,"Unable to append to building commandlne"); + rc=CLI_ERROR; + } + } else { + if (!(cmdline=cli_int_buildmode_extend_cmdline(cmdline,optarg->name))) { + cli_error(cli,"Unable to append to building commandlne"); + rc=CLI_ERROR; + } + if (!(cmdline=cli_int_buildmode_extend_cmdline(cmdline,value))) { + cli_error(cli,"Unable to append to building commandlne"); + rc=CLI_ERROR; + } + } + } + } while (rc==CLI_OK && value && optarg->flags & CLI_CMD_OPTION_MULTIPLE); + } + + if (rc==CLI_OK) { + cli_int_free_buildmode(cli); + cli_add_history(cli, cmdline); + rc = cli_run_command(cli, cmdline); + } + free_z(cmdline); + cli_int_free_buildmode(cli); + return rc; +} + + + +char * cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { + char *tptr=NULL; + char *cptr=NULL; + size_t oldlen=strlen(cmdline); + size_t wordlen=strlen(word); + int add_quotes=0; + + /* + * Allocate enough space to hold the old string, a space, and the new string (including null terminator). + * Also include enough space for a quote around the string if it contains a whitespace character + */ + if ( (tptr=(char*)realloc(cmdline, oldlen+1+wordlen+1+2)) ) { + strcat(tptr," "); + for (cptr=word;*cptr;cptr++) { + if (isspace(*cptr)) { + add_quotes=1; + break; + } + } + if (add_quotes) strcat(tptr,"'"); + strcat(tptr,word); + if (add_quotes) strcat(tptr,"'"); + } + return tptr; +} + + +int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + int rc=CLI_BUILDMODE_COMMAND_EXTEND; + + if (argc) { + cli_error(cli, "Extra arguments on command line, command ignored."); + rc = CLI_ERROR; + } + return rc; +} + +// a 'flag' callback has no optargs, so we need to set it ourself based on *this* command +int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + int rc=CLI_BUILDMODE_COMMAND_EXTEND; + + if (argc) { + cli_error(cli, "Extra arguments on command line, command ignored."); + rc = CLI_ERROR; + } + if (cli_int_add_optarg_value(cli, command, command, 0)) { + cli_error(cli,"Problem setting value for optional flag %s", command); + rc = CLI_ERROR; + } + return rc; +} + +// a 'flag' callback has no optargs, so we need to set it ourself based on *this* command +int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + int rc=CLI_BUILDMODE_COMMAND_EXTEND; + + if (argc) { + cli_error(cli, "Extra arguments on command line, command ignored."); + rc = CLI_ERROR; + } + if (cli_int_add_optarg_value(cli, command, command, CLI_CMD_OPTION_MULTIPLE)) { + cli_error(cli,"Problem setting value for optional flag %s", command); + rc = CLI_ERROR; + } + + return rc; +} + +int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + int rc= CLI_BUILDMODE_COMMAND_CANCEL; + + if (argc>0) { + cli_error(cli, "Extra arguments on command line, command ignored."); + rc=CLI_ERROR; + } + return rc; +} + +int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + int rc= CLI_BUILDMODE_COMMAND_EXIT; + + if (argc>0) { + cli_error(cli, "Extra arguments on command line, command ignored."); + rc=CLI_ERROR; + } + return rc; +} + +int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + struct cli_optarg_pair *optarg_pair; + if (cli && cli->buildmode ){ + for (optarg_pair=cli->found_optargs; optarg_pair; optarg_pair=optarg_pair->next) + { + // only show vars that are also current 'commands' + struct cli_command *c = cli->buildmode->commands; + for (;c ; c=c->next) { + if (!strcmp(c->command, optarg_pair->name)) { + cli_print(cli, " %-20s = %s", optarg_pair->name, optarg_pair->value); + break; + } + } + } + } + return CLI_OK; +} + +int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { + // iterate over our 'set' variables to see if that variable is also a 'valid' command right now + + struct cli_command *c; + + // is this 'optarg' to remove one of the current commands? + for (c=cli->buildmode->commands; c; c=c->next) { + if (cli->privilege < c->privilege) continue; + if ((cli->buildmode->mode!=c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY)) continue; + if (strcmp(c->command, argv[0])) continue; + // Ok, go fry anything by this name + + cli_int_unset_optarg_value(cli, argv[0]); + break; + } + + return CLI_OK; +} + +/* Generate a list of variables that *have* been set */ +int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { + return CLI_OK; +} + +int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { + return CLI_OK; +} + + +void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { + cli->transient_mode = transient_mode; +} + +int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry) { + int retval = CLI_ERROR; + if (comphelp && entry) { + char *dupelement = strdup(entry); + char **duparray = (char **)realloc((void*)comphelp->entries, sizeof(char *)*(comphelp->num_entries+1)); + if (dupelement && duparray) { + comphelp->entries=duparray; + comphelp->entries[comphelp->num_entries++] = dupelement; + retval = CLI_OK; + } + else { + free_z(dupelement); + free_z(duparray); + } + } + return retval; +} + +void cli_free_comphelp(struct cli_comphelp *comphelp) { + if (comphelp) { + int idx; + + for (idx=0;idxnum_entries;idx++) free_z(comphelp->entries[idx]); + free_z(comphelp->entries); + } +} + +static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, + int start_word, struct cli_pipeline_stage *stage) { + struct cli_command *c, *again_config = NULL, *again_any = NULL; + int c_words = stage->num_words; + + for (c = commands; c; c = c->next) { + if (cli->privilege < c->privilege) continue; + + if (strncasecmp(c->command, stage->words[start_word], c->unique_len)) continue; + + if (strncasecmp(c->command, stage->words[start_word], strlen(stage->words[start_word]))) continue; + + AGAIN: + if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL)) { + int rc = CLI_OK; + + // Found a word! + if (!c->children) { + // Last word + if (!c->callback && !c->filter) { + cli_error(cli, "No callback for \"%s\"", cli_command_name(cli, c)); + return CLI_ERROR; + } + } else { + if (start_word == c_words - 1) { + if (c->callback) goto CORRECT_CHECKS; + + cli_error(cli, "Incomplete command"); + return CLI_ERROR; + } + rc = cli_int_locate_command(cli, c->children, start_word + 1, stage); + if (rc == CLI_ERROR_ARG) { + if (c->callback) { + rc = CLI_OK; + goto CORRECT_CHECKS; + } else { + cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", stage->words[start_word]); + } + } + return rc; + } + + if (!c->callback && !c->filter) { + cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c)); + return CLI_ERROR; + } + + + CORRECT_CHECKS: + + if (rc == CLI_OK) { + + stage->command = c; + stage->first_unmatched = start_word + 1; + stage->first_optarg = stage->first_unmatched; + cli_int_parse_optargs(cli, stage, c, '\0', NULL); + rc = stage->status; + } + return rc; + } else if (cli->mode > MODE_CONFIG && c->mode == MODE_CONFIG) { + // command matched but from another mode, + // remember it if we fail to find correct command + again_config = c; + } else if (c->mode == MODE_ANY) { + // command matched but for any mode, + // remember it if we fail to find correct command + again_any = c; + } + } + + // drop out of config submode if we have matched command on MODE_CONFIG + if (again_config) { + c = again_config; + goto AGAIN; + } + if (again_any) { + c = again_any; + goto AGAIN; + } + + if (start_word == 0) + cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", stage->words[start_word]); + + return CLI_ERROR_ARG; +} + +int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { + + int i; + int rc=CLI_OK; + + struct cli_command *first_command=NULL; + cli->found_optargs=NULL; + if (cli->buildmode && cli->buildmode->commands) { + first_command = cli->buildmode->commands; + } else { + first_command = cli->commands; + } + for (i=0;inum_stages;i++) { + // in 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs + + if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; + + rc=cli_int_locate_command(cli, (i==0)?first_command:cli->filter_commands, 0, &pipeline->stage[i]); + + // and save our found optargs for later use + + if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; + else pipeline->stage[i].found_optargs = cli->found_optargs; + + if (rc!=CLI_OK) break; + } + return rc; +} + + + +struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline) { + int i; + if (pipeline) { + for (i=0;inum_stages;i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs); + for (i=0;inum_words;i++) free_z(pipeline->words[i]); + free_z(pipeline->cmdline); + free_z(pipeline); + pipeline=NULL; + } + return pipeline; +} + +void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { + int i,j; + struct cli_pipeline_stage* stage; + char **word; + struct cli_optarg_pair *optarg_pair; + + for (i=0,word=pipeline->words;inum_words;i++,word++) printf("[%s] ",*word); + printf("\n"); + printf( "#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words); + + for (i=0;inum_stages;i++) { + stage=&(pipeline->stage[i]); + printf( " #%d(%d words) first_unmatched=%d: " , i, stage->num_words, stage->first_unmatched); + for (j=0; jnum_words;j++) { + printf(" [%s]",stage->words[j]); + } + printf("\n"); + + if (stage->command) { + printf(" Command: %s\n", stage->command->command); + } + for (optarg_pair = stage->found_optargs; optarg_pair; optarg_pair=optarg_pair->next) { + printf(" %s: %s\n", optarg_pair->name, optarg_pair->value); + } + + } +} + +/* + * Take an array of words and return a pipeline, using '|' to split command into different 'stages'. + * Pipeline is broken down by '|' characters and within each p + */ +struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command) { + int i; + struct cli_pipeline_stage *stage; + char **word; + struct cli_pipeline *pipeline=NULL; + + cli->found_optargs=NULL; + if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; + if (!command) return NULL; + while (*command && isspace(*command)) command++; + + if (!(pipeline = (struct cli_pipeline *)calloc(1, sizeof(struct cli_pipeline)))) return NULL; + pipeline->cmdline=(char*)strdup(command); + + pipeline->num_words = cli_parse_line(command, pipeline->words, CLI_MAX_LINE_WORDS); + + pipeline->stage[0].num_words=0; + stage=&pipeline->stage[0]; + word = pipeline->words; + stage->words = word; + for (i=0 ; i < pipeline->num_words ; i++, word++) { + if (*word[0] == '|') { + if (cli->buildmode ) { + // can't allow filters in buildmode commands + cli_int_free_pipeline(pipeline); + return NULL; + } + stage->stage_num = pipeline->num_stages; + stage++; + stage->num_words = 0; + pipeline->num_stages++; + stage->words = word+1; // first word of the next stage is one past where we are (possibly NULL) + } + else + { + stage->num_words++; + } + } + stage->stage_num=pipeline->num_stages; + pipeline->num_stages++; + return pipeline; +} + +int cli_int_execute_pipeline (struct cli_def *cli, struct cli_pipeline *pipeline) { + int stage_num; + int rc=CLI_OK; + struct cli_filter **filt = &cli->filters; + + if (pipeline) { + for (stage_num=1;stage_numnum_stages;stage_num++) { + struct cli_pipeline_stage *stage=&pipeline->stage[stage_num]; + cli->found_optargs = stage->found_optargs; + *filt=calloc(sizeof(struct cli_filter), 1); + if (*filt) { + if ((rc=stage->command->init(cli, stage->num_words, stage->words, *filt)!=CLI_OK)) { + break; + } + filt = &(*filt)->next; + } + } + + // Did everything init? If so, execute, otherwise skip execution + if ( (rc==CLI_OK) && pipeline->stage[0].command->callback) { + struct cli_pipeline_stage *stage=&pipeline->stage[0]; + + if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; + else cli->found_optargs = pipeline->stage[0].found_optargs; + rc=stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words+stage->first_unmatched, stage->num_words - stage->first_unmatched); + if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs ; + } + } + // Now teardown any filters + while (cli->filters) { + struct cli_filter *filt = cli->filters; + if (filt->filter) filt->filter(cli, NULL, cli->filters->data); + cli->filters=filt->next; + free_z(filt); + } + cli->found_optargs=NULL; + return rc; +} + +static char DELIM_OPT_START[]="["; +static char DELIM_OPT_END[]="]"; +static char DELIM_ARG_START[]="<"; +static char DELIM_ARG_END[]=">"; +static char DELIM_NONE[]=""; +static char BUILDMODE_YES[] = " (enter buildmode)"; +static char BUILDMODE_NO[] = ""; + +static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *optarg, struct cli_comphelp *comphelp, + int num_candidates, const char lastchar, const char *anchor_word, const char *next_word) { + int help_insert=0; + char *delim_start=DELIM_NONE; + + char *delim_end=DELIM_NONE; + char *allow_buildmode=BUILDMODE_NO; + int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL; + char *tptr=NULL; + + // if we've already seen a value by this exact name, skip it, unless the multiple flag is set + if (cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) return; + + get_completions = optarg->get_completions; + if (optarg->flags & CLI_CMD_OPTIONAL_FLAG) { + if (!( anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { + delim_start=DELIM_OPT_START; + delim_end=DELIM_OPT_END; + get_completions = NULL; // no point, completor of field is the name itself + } + } else if (optarg->flags & CLI_CMD_ARGUMENT) { + delim_start=DELIM_ARG_START; + delim_end=DELIM_ARG_END; + } else if (optarg->flags & CLI_CMD_OPTIONAL_ARGUMENT) { + /* + * optional args can match against the name the value. + * Here 'anchor_word' is the name, and 'next_word' is what we're matching against. + * So if anchor_word==next_word we're looking at the 'name' of the optarg, otherwise we + * know the name and are going against the value. + */ + if (anchor_word!=next_word) { + // matching against optional argument 'value' + help_insert = 0; + if (!get_completions) { + delim_start=DELIM_ARG_START; + delim_end=DELIM_ARG_END; + } + } else { + // matching against optional argument 'name' + help_insert = 1; + get_completions=NULL; // matching against the name, not the following field value + if (!( anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { + delim_start=DELIM_OPT_START; + delim_end=DELIM_OPT_END; + } + } + } + + // Fill in with help text or completor value(s) as indicated + if ((lastchar == '?') && (asprintf(&tptr, "%s%s%s", delim_start,optarg->name,delim_end) != -1)) { + if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) allow_buildmode=BUILDMODE_YES; + if (help_insert && (asprintf(&tptr, " %-20s enter '%s' to %s%s", tptr, optarg->name, (optarg->help)?optarg->help : "",allow_buildmode)!=-1)) { + cli_add_comphelp_entry(comphelp, tptr); + free_z(tptr); + } + else if (asprintf(&tptr, " %-20s %s%s", tptr, (optarg->help)?optarg->help : "",allow_buildmode)!=-1) { + cli_add_comphelp_entry(comphelp, tptr); + free_z(tptr); + } + } else if (lastchar == CTRL('I')) { + if (get_completions) { + (*get_completions)(cli, optarg->name, next_word, comphelp); + } else if ( (!anchor_word || !strncmp(anchor_word, optarg->name, strlen(anchor_word))) && (asprintf(&tptr, "%s%s%s", delim_start,optarg->name,delim_end) != -1)) { + cli_add_comphelp_entry(comphelp, tptr); + free_z(tptr); + } + } +} + + +static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp) { + struct cli_optarg *optarg=NULL, *oaptr=NULL; + int w_idx; // word_index + int w_incr; // word_increment + struct cli_optarg *candidates[CLI_MAX_LINE_WORDS]; + int num_candidates=0; + int c_idx; // candidate_idx + char *value; + int is_last_word=0; + int (*validator)(struct cli_def*,const char *name, const char *value); + + /* + * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to + * know what has already been found. There should be no ambiguities before the 'last' word. + * Note specifically that for tab completions and help the *last* word can be a null pointer. + */ + stage->error_word=NULL; + + /* start our optarg and word pointers at the beginning + * optarg will be incremented *only* when an argument is identified + * w_idx will be incremented either by 1 (optflag or argument) or 2 (optional argument) + */ + w_idx=stage->first_unmatched; + optarg=cmd->optargs; + num_candidates=0; + while (optarg && w_idxnum_words && (num_candidates <=1)) { + num_candidates=0; + w_incr = 1; // assume we're only incrementing by a word - if we match an optional argument bump to 2 + + /* the initial loop here is to identify candidates based matching *this* word in order against: + * an exact match of the word to the optinal flag/argument name (yield exactly one match and exit the loop) + * a partial match for optional flag/argument name + * candidate an argument. + */ + + for (oaptr=optarg;oaptr;oaptr=oaptr->next) { + // Skip this option unless it matches privileges, MODE_ANY, the current mode, or the transient_mode + if (cli->privilege < oaptr->privilege) continue; + if ((oaptr->mode!=cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue; + + + /* An exact match for an optional flag/argument name trumps anything and will be the *only* candidate + * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a candidate + * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are required + */ + if (stage->words[w_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG|CLI_CMD_OPTIONAL_ARGUMENT)) && !strcmp(oaptr->name, stage->words[w_idx])) { + candidates[0]=oaptr; + num_candidates=1; + break; + } else if (!stage->words[w_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || !strncasecmp(oaptr->name, stage->words[w_idx], strlen(stage->words[w_idx])) ) { + candidates[num_candidates++] = oaptr; + } + if (oaptr->flags & CLI_CMD_ARGUMENT) { + break; + } + + } + + /* + * Iterate over the list of candidates for this word. There are several early exit cases to consider: + * - If we have no candidates then we're done - any remaining words must be processed by the command callback + * - If we have more than one candidate evaluating for execution punt hard after complaining. + * - If we have more than one candidate and we're not at end-of-line ( + */ + if (num_candidates==0) break; + if ((num_candidates>1) && ((lastchar=='\0') || (w_idx<(stage->num_words-1)))) { + stage->error_word=stage->words[w_idx]; + stage->status = CLI_AMBIGUOUS; + cli_error(cli,"Ambiguous option/argument for command %s", stage->command->command); + return; + } + + /* + * So now we could have one or more candidates. We need to call get help/completions *only* if this is the 'last-word' + * Remember that last word for optinal arguments is last or next to last.... + */ + if (lastchar!='\0') { + int called_comphelp=0; + for (c_idx=0;c_idxflags&(CLI_CMD_OPTIONAL_FLAG|CLI_CMD_ARGUMENT)) && (w_idx==(stage->num_words-1))) || + ((oaptr->flags&CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx==(stage->num_words-1))) ) { + cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], stage->words[w_idx]); + called_comphelp=1; + } else if ((oaptr->flags&CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx==(stage->num_words-2))) { + cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], stage->words[w_idx+1]); + called_comphelp=1; + } + } + // if we were 'end-of-word' and looked for completions/help, return to user + if (called_comphelp) { + stage->status = CLI_OK; + return; + } + } + + // set some values for use later - makes code much easier to read + if ( ((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG|CLI_CMD_ARGUMENT)) && (w_idx==(stage->num_words-1))) || + ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx==(stage->num_words-2))) ) { + is_last_word=1; + } + value = stage->words[w_idx]; + oaptr = candidates[0]; + validator = oaptr->validator; + + if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT)) { + w_incr = 2; + value = stage->words[w_idx+1]; + if (!value && lastchar=='\0') { + // hit a optional argument that does not have a value with it + cli_error(cli,"Optional argument %s requires a value", stage->words[w_idx]); + stage->error_word = stage->words[w_idx]; + stage->status = CLI_MISSING_VALUE; + return ; + } + } + + /* + * Ok, so we're not at end of string and doing help/completions. + * So see if our value is 'valid', to save it, and see if we have any extra processing to + * do such as a transient mode check or enter build mode. + */ + + if (!validator || ((*validator)(cli, oaptr->name, value)==CLI_OK)) { + if (oaptr->flags & CLI_CMD_DO_NOT_RECORD) { + // we want completion and validation, but then leave this 'value' to be seen + // as argv[0] with argc=1 + break; + } else if (cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE)) { + cli_error(cli,"%sProblem setting value for command argument %s", lastchar=='\0'?"" : "\n", stage->words[w_idx]); + cli_reprompt(cli); + stage->error_word=stage->words[w_idx]; + stage->status = CLI_ERROR; + return; + } + } else { + cli_error(cli,"%sProblem parsing command setting %s with value %s", lastchar=='\0'? "": "\n", oaptr->name, stage->words[w_idx]); + cli_reprompt(cli); + stage->error_word=stage->words[w_idx]; + stage->status = CLI_ERROR; + return; + } + + + // if this optarg can set the transient mode, then evaluate it if we're not at last word + if (oaptr->transient_mode && (oaptr->transient_mode(cli, oaptr->name, value))) { + stage->error_word = stage->words[w_idx]; + stage->status = CLI_ERROR; + return ; + } + + // only do buildmode optargs if we're a executing a command, parsing command (stage 0), and this is the last word + if ( (stage->status==CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { + stage->status=cli_int_enter_buildmode(cli, stage, value); + return ; + } + + /* + * Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg starting point + * we see a true argument + */ + if (oaptr->flags & CLI_CMD_ARGUMENT) { + // advance pass this argument entry + optarg = oaptr->next; + } + + w_idx+= w_incr; + stage->first_unmatched = w_idx; + } + + + // Ok, if we're evaluating the command for execution, ensure we have all required arguments. + if (lastchar == '\0') { + for(; optarg; optarg= optarg->next) { + if (cli->privilege < optarg->privilege) continue; + if ((optarg->mode!=cli->mode) && (optarg->mode != cli->transient_mode) && (optarg->mode != MODE_ANY)) continue; + if (optarg->flags & CLI_CMD_DO_NOT_RECORD) continue; + if (optarg->flags & CLI_CMD_ARGUMENT) { + cli_error(cli, "Incomplete command, missing required argument '%s'",optarg->name); + stage->status=CLI_MISSING_ARGUMENT; + return; + } + } + } + return ; +} + + diff --git a/libcli.h b/libcli.h index 6ee68e5..1b67332 100644 --- a/libcli.h +++ b/libcli.h @@ -12,14 +12,23 @@ extern "C" { #include #define LIBCLI_VERSION_MAJOR 1 -#define LIBCLI_VERISON_MINOR 9 -#define LIBCLI_VERISON_REVISION 8 +#define LIBCLI_VERISON_MINOR 10 +#define LIBCLI_VERISON_REVISION 0 #define LIBCLI_VERSION ((LIBCLI_VERSION_MAJOR << 16) | (LIBCLI_VERSION_MINOR << 8) | LIBCLI_VERSION_REVISION) #define CLI_OK 0 #define CLI_ERROR -1 #define CLI_QUIT -2 #define CLI_ERROR_ARG -3 +#define CLI_AMBIGUOUS -4 +#define CLI_UNRECOGNIZED -5 +#define CLI_MISSING_ARGUMENT -6 +#define CLI_MISSING_VALUE -7 +#define CLI_BUILDMODE_COMMAND_START -8 +#define CLI_BUILDMODE_COMMAND_ERROR -9 +#define CLI_BUILDMODE_COMMAND_EXTEND -10 +#define CLI_BUILDMODE_COMMAND_CANCEL -11 +#define CLI_BUILDMODE_COMMAND_EXIT -12 #define MAX_HISTORY 256 @@ -70,6 +79,11 @@ struct cli_def { time_t last_action; int telnet_protocol; void *user_context; + struct cli_command *filter_commands; + struct cli_optarg_pair *found_optargs; + int transient_mode; + struct cli_pipeline *pipeline; + struct cli_buildmode *buildmode; }; struct cli_filter { @@ -78,6 +92,8 @@ struct cli_filter { struct cli_filter *next; }; +enum command_types { CLI_REGULAR_COMMAND, CLI_FILTER_COMMAND, CLI_BUILDMODE_COMMAND }; + struct cli_command { char *command; int (*callback)(struct cli_def *, const char *, char **, int); @@ -88,6 +104,77 @@ struct cli_command { struct cli_command *next; struct cli_command *children; struct cli_command *parent; + struct cli_optarg *optargs; + int (*filter)(struct cli_def *cli, const char *string, void *data); + int (*init) (struct cli_def *cli, int, char **, struct cli_filter *filt); + int command_type; +}; + + +struct cli_comphelp { + int comma_separated; + char **entries; + int num_entries; +}; + +enum optarg_flags { CLI_CMD_OPTIONAL_FLAG = 1 << 0, + CLI_CMD_OPTIONAL_ARGUMENT = 1 << 1, + CLI_CMD_ARGUMENT = 1 << 2, + CLI_CMD_ALLOW_BUILDMODE = 1 << 3, + CLI_CMD_OPTION_MULTIPLE = 1 << 4, + CLI_CMD_OPTION_SEEN = 1 << 5, + CLI_CMD_TRANSIENT_MODE = 1 << 6, + CLI_CMD_DO_NOT_RECORD = 1 << 7, + }; + + +struct cli_optarg { + char *name; + int flags; + char *help; + int mode; + int privilege; + unsigned int unique_len; + int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *); + int (*validator)(struct cli_def *, const char *, const char *); + int (*transient_mode)(struct cli_def *, const char *, const char *); + struct cli_optarg *next; +}; + +struct cli_optarg_pair { + char *name; + char *value; + struct cli_optarg_pair *next; +}; + +struct cli_pipeline_stage { + struct cli_command *command; + struct cli_optarg_pair *found_optargs; + char **words; + int num_words; + int status; + int first_unmatched; + int first_optarg; + int stage_num; + char *error_word; +}; + +struct cli_pipeline { + char *cmdline; + char *words[CLI_MAX_LINE_WORDS]; + int num_words; + int num_stages; + struct cli_pipeline_stage stage[CLI_MAX_LINE_WORDS]; +}; + +struct cli_buildmode { + struct cli_command *c; + struct cli_command *commands; + struct cli_optarg_pair *found_optargs; + char *cname; + int mode; + int transient_mode; + char * mode_text; }; struct cli_def *cli_init(); @@ -130,6 +217,25 @@ void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol); void cli_set_context(struct cli_def *cli, void *context); void *cli_get_context(struct cli_def *cli); +void cli_free_comphelp(struct cli_comphelp *comphelp); +int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry); +void cli_set_transient_mode(struct cli_def *cli, int transient_mode); +struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, + int(*init) (struct cli_def *cli, int, char **, struct cli_filter *filt), + int(*filter)(struct cli_def *, const char *, void *), + int privilege, int mode, const char *help); +int cli_unregister_filter(struct cli_def *cli, const char *command); +int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int priviledge, int mode, const char *help, + int (*get_completions)(struct cli_def *cli, const char*, const char *, struct cli_comphelp * ), + int (*validator)(struct cli_def *cli, const char *, const char *), + int (*transient_mode)(struct cli_def *, const char *, const char *)); +char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) ; +struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); +int cli_unregister_optarg(struct cli_command *cmd, const char *name); +char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); +void cli_unregister_all(struct cli_def *cli, struct cli_command *command); +void cli_unregister_all_optarg(struct cli_optarg *optarg) ; + #ifdef __cplusplus } #endif diff --git a/libcli.spec b/libcli.spec index fd3daa5..e653cb6 100644 --- a/libcli.spec +++ b/libcli.spec @@ -1,7 +1,7 @@ -Version: 1.9.8 +Version: 1.10.0 Summary: Cisco-like telnet command-line library Name: libcli -Release: 4 +Release: 1 License: LGPL Group: Library/Communication Source: %{name}-%{version}.tar.gz @@ -67,6 +67,19 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Mon Jul 16 2019 Rob Sanders 1.10.0-1 +- Add support for named arguments, optional flags, and optional arguments +- Support help and tab complete for options/arguments +- Enable users to add custom 'filters', including support for options/arguments +- Replaced current interal filters with equivalent 'user filters' +- Added examples of options/arguments to clitest +- Added support for 'building' longer commands one option/argument at a time (buildmode) +- Additional minor Coverity/valgrind related fixes +- Tab completion will show up until an ambiguity, or entire entry if only one found +- Tab completion for options/arguments will show '[]' around optional items +- Tab completion for options/arguments will show '<>' around required items +- Restructured clitest.c so 'cli_init()' is done in child thread + * Wed Sep 19 2018 Rob Sanders 1.9.8-4 - Update spac file to use relative links for libcli.so symlinks From 1b1c94c9f55c52879e5bc369bdbbc3556e16b354 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Mon, 15 Jul 2019 16:18:50 -0400 Subject: [PATCH 02/25] Update clitest with optarg using 'multiple' flag --- clitest.c | 29 ++++++++++++++++++++++++++--- libcli.c | 14 +++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/clitest.c b/clitest.c index e462283..3e9edf8 100644 --- a/clitest.c +++ b/clitest.c @@ -183,12 +183,22 @@ int cmd_perimeter( struct cli_def *cli, const char *command, char *argv[], int a struct cli_optarg_pair *optargs=cli_get_all_found_optargs(cli); int i=1,numSides=0; int perimeter=0; + int verbose_count=0; + char *verboseArg=NULL; char *shapeName=NULL; cli_print(cli, "perimeter callback, with %d args" , argc); for (;optargs;optargs=optargs->next) cli_print(cli, "%d, %s=%s", i++, optargs->name, optargs->value); + + if ((verboseArg=cli_get_optarg_value(cli, "verbose", verboseArg))) { + do { + verbose_count++; + } while ((verboseArg=cli_get_optarg_value(cli, "verbose", verboseArg))); + } + cli_print(cli, "verbose argument was seen %d times", verbose_count); + shapeName = cli_get_optarg_value(cli, "shape", NULL); if (!shapeName) { cli_error(cli, "No shape name given"); @@ -215,12 +225,12 @@ int cmd_perimeter( struct cli_def *cli, const char *command, char *argv[], int a const char *KnownShapes[] = {"rectangle","triangle", NULL}; -int shape_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { +int shape_completor(struct cli_def *cli, const char *name, const char *value, struct cli_comphelp *comphelp) { const char **shape ; int rc=CLI_OK; - printf ("Calling shape_completor given %s" , word); + printf("shape_completor called with <%s>\n", value); for (shape=KnownShapes; *shape && (rc==CLI_OK);shape++) { - if (!word || !strncmp(*shape, word, strlen(word))) { + if (!value || !strncmp(*shape, value, strlen(value))) { rc=cli_add_comphelp_entry(comphelp, *shape); } } @@ -230,14 +240,22 @@ int shape_completor(struct cli_def *cli, const char *name, const char *word, str int shape_validator(struct cli_def *cli, const char *name, const char *value) { const char **shape; int rc=CLI_ERROR; + printf("shape_validator called with <%s>\n", value); for (shape=KnownShapes; *shape; shape++) { if (!strcmp(value, *shape)) return CLI_OK; } return rc; } +int verbose_validator(struct cli_def *cli, const char *name, const char *value) { + int rc=CLI_OK; + printf ("verbose_validator called\n"); + return rc; +} + int shape_transient_eval( struct cli_def *cli, const char *name, const char *value) { int rc=CLI_OK; + printf("shape_transient_eval called with <%s>\n", value); if ( !strcmp(value,"rectangle")) { cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE); rc=CLI_OK; @@ -258,6 +276,7 @@ int color_completor(struct cli_def *cli, const char *name, const char *word, str // Attempt to show matches against the following color strings const char **color ; int rc=CLI_OK; + printf("color_completor called with <%s>\n", word); for (color=KnownColors; *color && (rc==CLI_OK);color++) { if (!word || !strncmp(*color, word, strlen(word))) { rc=cli_add_comphelp_entry(comphelp, *color); @@ -269,6 +288,7 @@ int color_completor(struct cli_def *cli, const char *name, const char *word, str int color_validator(struct cli_def *cli, const char *name, const char *value) { const char **color; int rc=CLI_ERROR; + printf("color_validator called for %s\n", name); for (color=KnownColors; *color; color++) { if (!strcmp(value, *color)) return CLI_OK; } @@ -281,6 +301,7 @@ int side_length_validator(struct cli_def *cli, const char *name, const char *val char *endptr; int rc = CLI_OK; + printf("side_length_validator called\n"); errno=0; len = strtol (value, &endptr, 10); if ((endptr==value) || (*endptr != '\0') || ((errno==ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX))) )return CLI_ERROR; @@ -328,6 +349,8 @@ void run_child(int x) { c=cli_register_command(cli, NULL, "perimeter", cmd_perimeter, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Calculate perimeter of polygon"); cli_register_optarg(c, "transparent", CLI_CMD_OPTIONAL_FLAG, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", NULL, NULL, NULL); + cli_register_optarg(c, "verbose", CLI_CMD_OPTIONAL_FLAG|CLI_CMD_OPTION_MULTIPLE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", + NULL, NULL, NULL); cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT|CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape to calclate perimeter for", shape_completor, shape_validator, shape_transient_eval); cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", diff --git a/libcli.c b/libcli.c index bc00348..fc8c50e 100644 --- a/libcli.c +++ b/libcli.c @@ -2232,14 +2232,22 @@ char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_af char *value=NULL; struct cli_optarg_pair *optarg_pair; - for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair=optarg_pair->next) { + printf("cli_get_optarg_value entry - looking for <%s> after <%p>\n", name, (void*)find_after); + for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair=optarg_pair->next) { + printf(" Checking %s with value %s <%p> \n", optarg_pair->name, optarg_pair->value, (void*)optarg_pair->value); + + // check next entry if this isn't our name if (strcasecmp(optarg_pair->name, name)) continue; - if (find_after && (optarg_pair->value == find_after)) { + + // did we have a find_after, then ignore anything up until our find_after match + if ((find_after )&& (optarg_pair->value == find_after)) { find_after=NULL; continue; + } else if ( !find_after) { + value = optarg_pair->value; } - value = optarg_pair->value; } + printf("cli_get_optarg_value exit - returning <%s><%p>\n", value, value); return value; } From 7aa5cafceaf21683e311edf1b1b13bf78e6bdb99 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Mon, 15 Jul 2019 16:37:21 -0400 Subject: [PATCH 03/25] Update optarg processing with flag for combining remaining text on line as one argument --- libcli.c | 29 +++++++++++++++++++++-------- libcli.h | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/libcli.c b/libcli.c index fc8c50e..17d4692 100644 --- a/libcli.c +++ b/libcli.c @@ -2247,7 +2247,7 @@ char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_af value = optarg_pair->value; } } - printf("cli_get_optarg_value exit - returning <%s><%p>\n", value, value); + printf("cli_get_optarg_value exit - returning <%s><%p>\n", name, value); return value; } @@ -3062,15 +3062,28 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (!validator || ((*validator)(cli, oaptr->name, value)==CLI_OK)) { if (oaptr->flags & CLI_CMD_DO_NOT_RECORD) { - // we want completion and validation, but then leave this 'value' to be seen + // we want completion and validation, but then leave this 'value' to be seen - used *only* by buildmode // as argv[0] with argc=1 break; - } else if (cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE)) { - cli_error(cli,"%sProblem setting value for command argument %s", lastchar=='\0'?"" : "\n", stage->words[w_idx]); - cli_reprompt(cli); - stage->error_word=stage->words[w_idx]; - stage->status = CLI_ERROR; - return; + } else { + // need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing + int set_value_return = 0; + + if (oaptr->flags | CLI_CMD_REMAINDER_OF_LINE) { + char *combined=NULL; + combined = join_words(stage->num_words-w_idx, stage->words + w_idx); + set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); + free_z(combined); + } else { + set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE); + } + if (set_value_return != CLI_OK) { + cli_error(cli,"%sProblem setting value for command argument %s", lastchar=='\0'?"" : "\n", stage->words[w_idx]); + cli_reprompt(cli); + stage->error_word=stage->words[w_idx]; + stage->status = CLI_ERROR; + return; + } } } else { cli_error(cli,"%sProblem parsing command setting %s with value %s", lastchar=='\0'? "": "\n", oaptr->name, stage->words[w_idx]); diff --git a/libcli.h b/libcli.h index 1b67332..d5ddd48 100644 --- a/libcli.h +++ b/libcli.h @@ -125,6 +125,7 @@ enum optarg_flags { CLI_CMD_OPTIONAL_FLAG = 1 << 0, CLI_CMD_OPTION_SEEN = 1 << 5, CLI_CMD_TRANSIENT_MODE = 1 << 6, CLI_CMD_DO_NOT_RECORD = 1 << 7, + CLI_CMD_REMAINDER_OF_LINE = 1 << 8, }; From e20f6facd47c647962ae8191483b4c8f2f570e1a Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Mon, 15 Jul 2019 17:36:24 -0400 Subject: [PATCH 04/25] Fix bug on optarg cleanup in pipe, convert 'begin' filter to use optargs --- libcli.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/libcli.c b/libcli.c index 17d4692..958ed27 100644 --- a/libcli.c +++ b/libcli.c @@ -130,6 +130,7 @@ struct cli_filter_cmds { // Forward defines of *INTERNAL* library function as static here static int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); +static int cli_begin_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_match_filter(struct cli_def *cli, const char *string, void *data); static int cli_range_filter(struct cli_def *cli, const char *string, void *data); @@ -544,7 +545,8 @@ struct cli_def *cli_init() { // and now the built in filters - c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); + c = cli_register_filter(cli, "begin", cli_begin_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); + cli_register_optarg(c, "begin_at", CLI_CMD_ARGUMENT|CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match", NULL, NULL, NULL); c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Between lines that match"); @@ -1864,6 +1866,24 @@ struct cli_range_filter_state { char *to; }; +int cli_begin_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { + struct cli_range_filter_state *state; + char *from = strdup(cli_get_optarg_value(cli, "begin_at", NULL)); + char *to = 0; + + filt->filter = cli_range_filter; + filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1); + if (state) { + state->from = from; + state->to = to; + return CLI_OK; + } else { + free_z(from); + free_z(to); + return CLI_ERROR; + } +} + int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { struct cli_range_filter_state *state; char *from = 0; @@ -2716,7 +2736,7 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline // in 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; - + else cli->found_optargs = NULL; rc=cli_int_locate_command(cli, (i==0)?first_command:cli->filter_commands, 0, &pipeline->stage[i]); // and save our found optargs for later use @@ -3048,7 +3068,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (!value && lastchar=='\0') { // hit a optional argument that does not have a value with it cli_error(cli,"Optional argument %s requires a value", stage->words[w_idx]); - stage->error_word = stage->words[w_idx]; + stage->error_word = stage->words[w_idx]; stage->status = CLI_MISSING_VALUE; return ; } @@ -3069,7 +3089,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage // need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing int set_value_return = 0; - if (oaptr->flags | CLI_CMD_REMAINDER_OF_LINE) { +/* if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { char *combined=NULL; combined = join_words(stage->num_words-w_idx, stage->words + w_idx); set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); @@ -3077,6 +3097,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage } else { set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE); } +*/ set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE); if (set_value_return != CLI_OK) { cli_error(cli,"%sProblem setting value for command argument %s", lastchar=='\0'?"" : "\n", stage->words[w_idx]); cli_reprompt(cli); From 96ea74faec952a8f39096eb1e59daa539d8b1059 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Mon, 15 Jul 2019 17:38:36 -0400 Subject: [PATCH 05/25] Remove commented section around CLI_CMD_REMAINDER_OF_LINE processing --- libcli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcli.c b/libcli.c index 958ed27..cef5630 100644 --- a/libcli.c +++ b/libcli.c @@ -3089,7 +3089,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage // need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing int set_value_return = 0; -/* if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { + if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { char *combined=NULL; combined = join_words(stage->num_words-w_idx, stage->words + w_idx); set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); @@ -3097,7 +3097,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage } else { set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE); } -*/ set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE); + if (set_value_return != CLI_OK) { cli_error(cli,"%sProblem setting value for command argument %s", lastchar=='\0'?"" : "\n", stage->words[w_idx]); cli_reprompt(cli); From 08f4947b2915a7416617f6f45eef3657651b0edd Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 16 Jul 2019 07:43:02 -0400 Subject: [PATCH 06/25] Run code through clang-format - note that my version doesn't recognize "AllowShortFunctionsOnASingleLine" --- clitest.c | 166 ++++---- libcli.c | 1093 +++++++++++++++++++++++++++-------------------------- libcli.h | 58 +-- 3 files changed, 659 insertions(+), 658 deletions(-) diff --git a/clitest.c b/clitest.c index 3e9edf8..e38366c 100644 --- a/clitest.c +++ b/clitest.c @@ -163,75 +163,69 @@ int regular_callback(struct cli_def *cli) { return CLI_OK; } -int check_enable(const char *password) { - return !strcasecmp(password, "topsecret"); -} +int check_enable(const char *password) { return !strcasecmp(password, "topsecret"); } int idle_timeout(struct cli_def *cli) { cli_print(cli, "Custom idle timeout"); return CLI_QUIT; } -void pc(UNUSED(struct cli_def *cli), const char *string) { - printf("%s\n", string); -} +void pc(UNUSED(struct cli_def *cli), const char *string) { printf("%s\n", string); } #define MODE_POLYGON_TRIANGLE 20 #define MODE_POLYGON_RECTANGLE 21 -int cmd_perimeter( struct cli_def *cli, const char *command, char *argv[], int argc) { - struct cli_optarg_pair *optargs=cli_get_all_found_optargs(cli); - int i=1,numSides=0; - int perimeter=0; - int verbose_count=0; - char *verboseArg=NULL; - char *shapeName=NULL; - - cli_print(cli, "perimeter callback, with %d args" , argc); - for (;optargs;optargs=optargs->next) - cli_print(cli, "%d, %s=%s", i++, optargs->name, optargs->value); - - - if ((verboseArg=cli_get_optarg_value(cli, "verbose", verboseArg))) { +int cmd_perimeter(struct cli_def *cli, const char *command, char *argv[], int argc) { + struct cli_optarg_pair *optargs = cli_get_all_found_optargs(cli); + int i = 1, numSides = 0; + int perimeter = 0; + int verbose_count = 0; + char *verboseArg = NULL; + char *shapeName = NULL; + + cli_print(cli, "perimeter callback, with %d args", argc); + for (; optargs; optargs = optargs->next) cli_print(cli, "%d, %s=%s", i++, optargs->name, optargs->value); + + if ((verboseArg = cli_get_optarg_value(cli, "verbose", verboseArg))) { do { verbose_count++; - } while ((verboseArg=cli_get_optarg_value(cli, "verbose", verboseArg))); + } while ((verboseArg = cli_get_optarg_value(cli, "verbose", verboseArg))); } cli_print(cli, "verbose argument was seen %d times", verbose_count); - + shapeName = cli_get_optarg_value(cli, "shape", NULL); if (!shapeName) { cli_error(cli, "No shape name given"); return CLI_ERROR; - } else if (strcmp(shapeName,"triangle") ==0 ) { - numSides=3; - } else if (strcmp(shapeName,"rectangle") == 0 ) { - numSides=4; + } else if (strcmp(shapeName, "triangle") == 0) { + numSides = 3; + } else if (strcmp(shapeName, "rectangle") == 0) { + numSides = 4; } else { cli_error(cli, "Unrecognized shape given"); return CLI_ERROR; } - for (i=1;i<=numSides;i++) { + for (i = 1; i <= numSides; i++) { char sidename[50], *value; int length; - snprintf(sidename,50,"side_%d",i); + snprintf(sidename, 50, "side_%d", i); value = cli_get_optarg_value(cli, sidename, NULL); - length = strtol(value, NULL,10); + length = strtol(value, NULL, 10); perimeter += length; } cli_print(cli, "Perimeter is %d", perimeter); return CLI_OK; } -const char *KnownShapes[] = {"rectangle","triangle", NULL}; +const char *KnownShapes[] = {"rectangle", "triangle", NULL}; int shape_completor(struct cli_def *cli, const char *name, const char *value, struct cli_comphelp *comphelp) { - const char **shape ; - int rc=CLI_OK; + const char **shape; + int rc = CLI_OK; printf("shape_completor called with <%s>\n", value); - for (shape=KnownShapes; *shape && (rc==CLI_OK);shape++) { + for (shape = KnownShapes; *shape && (rc == CLI_OK); shape++) { if (!value || !strncmp(*shape, value, strlen(value))) { - rc=cli_add_comphelp_entry(comphelp, *shape); + rc = cli_add_comphelp_entry(comphelp, *shape); } } return rc; @@ -239,47 +233,48 @@ int shape_completor(struct cli_def *cli, const char *name, const char *value, st int shape_validator(struct cli_def *cli, const char *name, const char *value) { const char **shape; - int rc=CLI_ERROR; + int rc = CLI_ERROR; printf("shape_validator called with <%s>\n", value); - for (shape=KnownShapes; *shape; shape++) { + for (shape = KnownShapes; *shape; shape++) { if (!strcmp(value, *shape)) return CLI_OK; } return rc; } int verbose_validator(struct cli_def *cli, const char *name, const char *value) { - int rc=CLI_OK; - printf ("verbose_validator called\n"); + int rc = CLI_OK; + printf("verbose_validator called\n"); return rc; } -int shape_transient_eval( struct cli_def *cli, const char *name, const char *value) { - int rc=CLI_OK; +int shape_transient_eval(struct cli_def *cli, const char *name, const char *value) { + int rc = CLI_OK; printf("shape_transient_eval called with <%s>\n", value); - if ( !strcmp(value,"rectangle")) { + if (!strcmp(value, "rectangle")) { cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE); - rc=CLI_OK; + rc = CLI_OK; } else if (!strcmp(value, "triangle")) { cli_set_transient_mode(cli, MODE_POLYGON_TRIANGLE); - rc=CLI_OK; + rc = CLI_OK; } else { - cli_error(cli, "unrecognized value for setting %s -> %s" , name, value); - rc=CLI_ERROR; + cli_error(cli, "unrecognized value for setting %s -> %s", name, value); + rc = CLI_ERROR; } return rc; } -const char *KnownColors[] = {"black", "white","gray", "red","blue","green","lightred","lightblue","lightgreen", - "darkred","darkblue","darkgree","lavender", "yellow", NULL}; +const char *KnownColors[] = {"black", "white", "gray", "red", "blue", + "green", "lightred", "lightblue", "lightgreen", "darkred", + "darkblue", "darkgree", "lavender", "yellow", NULL}; int color_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { // Attempt to show matches against the following color strings - const char **color ; - int rc=CLI_OK; + const char **color; + int rc = CLI_OK; printf("color_completor called with <%s>\n", word); - for (color=KnownColors; *color && (rc==CLI_OK);color++) { + for (color = KnownColors; *color && (rc == CLI_OK); color++) { if (!word || !strncmp(*color, word, strlen(word))) { - rc=cli_add_comphelp_entry(comphelp, *color); + rc = cli_add_comphelp_entry(comphelp, *color); } } return rc; @@ -287,9 +282,9 @@ int color_completor(struct cli_def *cli, const char *name, const char *word, str int color_validator(struct cli_def *cli, const char *name, const char *value) { const char **color; - int rc=CLI_ERROR; + int rc = CLI_ERROR; printf("color_validator called for %s\n", name); - for (color=KnownColors; *color; color++) { + for (color = KnownColors; *color; color++) { if (!strcmp(value, *color)) return CLI_OK; } return rc; @@ -300,11 +295,12 @@ int side_length_validator(struct cli_def *cli, const char *name, const char *val long len; char *endptr; int rc = CLI_OK; - + printf("side_length_validator called\n"); - errno=0; - len = strtol (value, &endptr, 10); - if ((endptr==value) || (*endptr != '\0') || ((errno==ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX))) )return CLI_ERROR; + errno = 0; + len = strtol(value, &endptr, 10); + if ((endptr == value) || (*endptr != '\0') || ((errno == ERANGE) && ((len == LONG_MIN) || (len == LONG_MAX)))) + return CLI_ERROR; return rc; } @@ -323,8 +319,8 @@ void run_child(int x) { cli_set_hostname(cli, "router"); cli_telnet_protocol(cli, 1); cli_regular(cli, regular_callback); - cli_regular_interval(cli, 5); // Defaults to 1 second -// cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second idle timeout + cli_regular_interval(cli, 5); // Defaults to 1 second + // cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second idle timeout cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); @@ -345,32 +341,32 @@ void run_child(int x) { "Enable cli_regular() callback debugging"); // Register some commands/subcommands to demonstrate opt/arg and buildmode operations - - c=cli_register_command(cli, NULL, "perimeter", cmd_perimeter, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Calculate perimeter of polygon"); - cli_register_optarg(c, "transparent", CLI_CMD_OPTIONAL_FLAG, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", - NULL, NULL, NULL); - cli_register_optarg(c, "verbose", CLI_CMD_OPTIONAL_FLAG|CLI_CMD_OPTION_MULTIPLE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", - NULL, NULL, NULL); - cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT|CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape to calclate perimeter for", - shape_completor, shape_validator, shape_transient_eval); + + c = cli_register_command(cli, NULL, "perimeter", cmd_perimeter, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, + "Calculate perimeter of polygon"); + cli_register_optarg(c, "transparent", CLI_CMD_OPTIONAL_FLAG, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, + "Set transparent flag", NULL, NULL, NULL); + cli_register_optarg(c, "verbose", CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTION_MULTIPLE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, + "Set transparent flag", NULL, NULL, NULL); + cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, + "Specify shape to calclate perimeter for", shape_completor, shape_validator, + shape_transient_eval); cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", - color_completor, color_validator, NULL); - cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 1 length", - NULL, side_length_validator, NULL); - cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 1 length", - NULL, side_length_validator, NULL); - cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 2 length", - NULL, side_length_validator, NULL); - cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 2 length", - NULL, side_length_validator, NULL); - cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 3 length", - NULL, side_length_validator, NULL); - cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 3 length", - NULL, side_length_validator, NULL); - cli_register_optarg(c, "side_4", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, "Specify side 4 length", - NULL, side_length_validator, NULL); - - + color_completor, color_validator, NULL); + cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, + "Specify side 1 length", NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, + "Specify side 1 length", NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, + "Specify side 2 length", NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_2", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, + "Specify side 2 length", NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, + "Specify side 3 length", NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_3", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, + "Specify side 3 length", NULL, side_length_validator, NULL); + cli_register_optarg(c, "side_4", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, + "Specify side 4 length", NULL, side_length_validator, NULL); // Set user context and its command cli_set_context(cli, (void *)&myctx); @@ -391,7 +387,7 @@ void run_child(int x) { fclose(fh); } } - cli_loop(cli,x); + cli_loop(cli, x); cli_done(cli); } diff --git a/libcli.c b/libcli.c index cef5630..e51e8be 100644 --- a/libcli.c +++ b/libcli.c @@ -37,13 +37,9 @@ * Stupid windows has multiple namespaces for filedescriptors, with different * read/write functions required for each .. */ -int read(int fd, void *buf, unsigned int count) { - return recv(fd, buf, count, 0); -} +int read(int fd, void *buf, unsigned int count) { return recv(fd, buf, count, 0); } -int write(int fd, const void *buf, unsigned int count) { - return send(fd, buf, count, 0); -} +int write(int fd, const void *buf, unsigned int count) { return send(fd, buf, count, 0); } int vasprintf(char **strp, const char *fmt, va_list args) { int size; @@ -105,7 +101,13 @@ int regex_dummy() { #define REG_ICASE 0 #endif -enum cli_states { STATE_LOGIN, STATE_PASSWORD, STATE_NORMAL, STATE_ENABLE_PASSWORD, STATE_ENABLE }; +enum cli_states { + STATE_LOGIN, + STATE_PASSWORD, + STATE_NORMAL, + STATE_ENABLE_PASSWORD, + STATE_ENABLE +}; struct unp { char *username; @@ -135,35 +137,36 @@ static int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, str static int cli_match_filter(struct cli_def *cli, const char *string, void *data); static int cli_range_filter(struct cli_def *cli, const char *string, void *data); static int cli_count_filter(struct cli_def *cli, const char *string, void *data); -static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp); +static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, + char lastchar, struct cli_comphelp *comphelp); static int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text); -static char * cli_int_buildmode_extend_cmdline(char *, char *word); +static char *cli_int_buildmode_extend_cmdline(char *, char *word); static void cli_int_free_buildmode(struct cli_def *cli); static int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); static void cli_free_command(struct cli_command *cmd); -static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)) ; -static struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, - int (*callback)(struct cli_def *cli, const char *, char **, int), - int privilege, int mode, const char *help) ; -static int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) ; -static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) ; -static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) ; +static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)); +static struct cli_command *cli_int_register_buildmode_command( + struct cli_def *cli, struct cli_command *parent, const char *command, + int (*callback)(struct cli_def *cli, const char *, char **, int), int privilege, int mode, const char *help); +static int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc); +static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, + struct cli_comphelp *comphelp); +static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value); static int cli_int_execute_buildmode(struct cli_def *cli); -static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) ; -//static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name ) ; +static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair); +// static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name ) ; static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command); -static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) ; -static int cli_int_execute_pipeline (struct cli_def *cli, struct cli_pipeline *pipeline); +static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); +static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline); - static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; ssize_t thisTime = 0; @@ -543,18 +546,25 @@ struct cli_def *cli_init() { cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Configure from the terminal"); - // and now the built in filters - c = cli_register_filter(cli, "begin", cli_begin_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); - cli_register_optarg(c, "begin_at", CLI_CMD_ARGUMENT|CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match", NULL, NULL, NULL); - - c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Between lines that match"); - - c = cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Count of lines"); - c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exclude lines that match"); - c = cli_register_filter(cli, "include", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match"); - c = cli_register_filter(cli, "grep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match regex (options: -v, -i, -e)"); - c = cli_register_filter(cli, "egrep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match extended regex"); + c = cli_register_filter(cli, "begin", cli_begin_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Begin with lines that match"); + cli_register_optarg(c, "begin_at", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Begin with lines that match", NULL, NULL, NULL); + + c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Between lines that match"); + + c = cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Count of lines"); + c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Exclude lines that match"); + c = cli_register_filter(cli, "include", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Include lines that match"); + c = cli_register_filter(cli, "grep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Include lines that match regex (options: -v, -i, -e)"); + c = cli_register_filter(cli, "egrep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Include lines that match extended regex"); cli->privilege = cli->mode = -1; cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); @@ -608,7 +618,7 @@ int cli_done(struct cli_def *cli) { cli_unregister_all(cli, cli->commands); cli_unregister_all(cli, cli->filter_commands); - if ( cli->buildmode) cli_int_free_buildmode(cli); + if (cli->buildmode) cli_int_free_buildmode(cli); free_z(cli->commandname); free_z(cli->modestring); free_z(cli->banner); @@ -713,18 +723,16 @@ static char *join_words(int argc, char **argv) { return p; } - int cli_run_command(struct cli_def *cli, const char *command) { - int rc=CLI_ERROR; - struct cli_pipeline *pipeline ; + int rc = CLI_ERROR; + struct cli_pipeline *pipeline; - // split command into pipeline stages, + // split command into pipeline stages, - pipeline=cli_int_generate_pipeline(cli, command); + pipeline = cli_int_generate_pipeline(cli, command); // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_COMMAND_START if found. - if (pipeline) rc=cli_int_validate_pipeline(cli, pipeline); - + if (pipeline) rc = cli_int_validate_pipeline(cli, pipeline); if (rc == CLI_OK) { rc = cli_int_execute_pipeline(cli, pipeline); @@ -733,91 +741,89 @@ int cli_run_command(struct cli_def *cli, const char *command) { return rc; } - - void cli_get_completions(struct cli_def *cli, const char *command, char lastchar, struct cli_comphelp *comphelp) { - struct cli_command *c=NULL; - struct cli_command *n=NULL; - struct cli_command *first_command=NULL; + struct cli_command *c = NULL; + struct cli_command *n = NULL; + struct cli_command *first_command = NULL; int i; - struct cli_pipeline *pipeline=NULL ; + struct cli_pipeline *pipeline = NULL; struct cli_pipeline_stage *stage; - - if (!(pipeline=cli_int_generate_pipeline(cli, command))) goto out; - stage=&pipeline->stage[pipeline->num_stages-1]; - + if (!(pipeline = cli_int_generate_pipeline(cli, command))) goto out; + + stage = &pipeline->stage[pipeline->num_stages - 1]; + // check to see if either *no* input, or if the lastchar is a tab. - if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words-1])) stage->num_words++; + if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words - 1])) + stage->num_words++; if (cli->buildmode && cli->buildmode->commands) { - first_command=cli->buildmode->commands; - } else if (pipeline->num_stages==1) { - first_command=cli->commands; + first_command = cli->buildmode->commands; + } else if (pipeline->num_stages == 1) { + first_command = cli->commands; } else { - first_command=cli->filter_commands; + first_command = cli->filter_commands; } - - for (c = first_command, i = 0; c && i < stage->num_words ; c = n) { - char *strptr=NULL; + + for (c = first_command, i = 0; c && i < stage->num_words; c = n) { + char *strptr = NULL; n = c->next; - + if (cli->privilege < c->privilege) continue; - if (c->mode != cli->mode && c->mode != MODE_ANY ) continue; + if (c->mode != cli->mode && c->mode != MODE_ANY) continue; if (stage->words[i] && strncasecmp(c->command, stage->words[i], strlen(stage->words[i]))) continue; // special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set if (cli->buildmode) { struct cli_optarg *optarg; - for (optarg = cli->buildmode->c->optargs; optarg; optarg=optarg->next) { + for (optarg = cli->buildmode->c->optargs; optarg; optarg = optarg->next) { if (!strcmp(optarg->name, c->command)) break; } - if (optarg && cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) continue; + if (optarg && cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) + continue; } - if (i < stage->num_words-1) { + if (i < stage->num_words - 1) { if (stage->words[i] && (strlen(stage->words[i]) < c->unique_len) && strcmp(stage->words[i], c->command)) continue; n = c->children; - - // if we have no more children, we've matched the *command* - remember this + + // if we have no more children, we've matched the *command* - remember this if (!c->children) { break; } - + i++; continue; } - + if ((lastchar == '?')) { - if (asprintf(&strptr," %-20s %s", c->command, (c->help)?c->help : "") != -1 ) { + if (asprintf(&strptr, " %-20s %s", c->command, (c->help) ? c->help : "") != -1) { cli_add_comphelp_entry(comphelp, strptr); free_z(strptr); } } else { - cli_add_comphelp_entry(comphelp, c->command); + cli_add_comphelp_entry(comphelp, c->command); } - } out: if (c) { // advance past first word of stage - i++; - stage->first_unmatched=i; - if (c->optargs ) { - + i++; + stage->first_unmatched = i; + if (c->optargs) { + cli_int_parse_optargs(cli, stage, c, lastchar, comphelp); - } else if (lastchar == '?') { + } else if (lastchar == '?') { // special case for getting help with no defined optargs.... comphelp->num_entries = -1; } } - pipeline=cli_int_free_pipeline(pipeline); - + pipeline = cli_int_free_pipeline(pipeline); } static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) { @@ -868,8 +874,7 @@ static int pass_matches(const char *pass, const char *try) { /* * TODO - find a small crypt(3) function for use on windows */ - if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) try - = crypt(try, pass); + if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) try = crypt(try, pass); #endif return !strcmp(pass, try); @@ -885,15 +890,14 @@ static int show_prompt(struct cli_def *cli, int sockfd) { if (cli->modestring) len += write(sockfd, cli->modestring, strlen(cli->modestring)); if (cli->buildmode) { len += write(sockfd, "[", 1); - len += write(sockfd, cli->buildmode->cname, strlen(cli->buildmode->cname)); + len += write(sockfd, cli->buildmode->cname, strlen(cli->buildmode->cname)); len += write(sockfd, "...", 3); if (cli->buildmode->mode_text) len += write(sockfd, cli->buildmode->mode_text, strlen(cli->buildmode->mode_text)); - len+= write(sockfd, "]", 1); + len += write(sockfd, "]", 1); } return len + write(sockfd, cli->promptchar, strlen(cli->promptchar)); } - int cli_loop(struct cli_def *cli, int sockfd) { int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0; char *cmd = NULL, *oldcmd = 0; @@ -966,13 +970,13 @@ int cli_loop(struct cli_def *cli, int sockfd) { while (1) { int sr; fd_set r; - + /* * ensure our transient mode is reset to the starting mode on *each* loop traversal * transient mode is valid only while a command is being evaluated/executed */ cli->transient_mode = cli->mode; - + if (cli->showprompt) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2); @@ -1265,14 +1269,14 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (cursor != l) continue; - cli_get_completions(cli, cmd, c, &comphelp); + cli_get_completions(cli, cmd, c, &comphelp); if (comphelp.num_entries == 0) { _write(sockfd, "\a", 1); } else if (lastchar == CTRL('I')) { // double tab int i; for (i = 0; i < comphelp.num_entries; i++) { - if (i % 4 == 0 ) + if (i % 4 == 0) _write(sockfd, "\r\n", 2); else _write(sockfd, " ", 1); @@ -1282,9 +1286,9 @@ int cli_loop(struct cli_def *cli, int sockfd) { cli->showprompt = 1; } else if (comphelp.num_entries == 1) { // Single completion - show *unless* the optional/required 'prefix' is present - if ((comphelp.entries[0][0]!='[') && (comphelp.entries[0][0]!='<')) { + if ((comphelp.entries[0][0] != '[') && (comphelp.entries[0][0] != '<')) { for (; l > 0; l--, cursor--) { - if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l-1]==',')) break; + if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); } strcpy((cmd + l), comphelp.entries[0]); @@ -1299,55 +1303,59 @@ int cli_loop(struct cli_def *cli, int sockfd) { // Yes, we had a match, but it wasn't required - remember the tab in case the user double tabs.... lastchar = CTRL('I'); } - } else if (comphelp.num_entries>1) { + } else if (comphelp.num_entries > 1) { /* More than one completion * Show as many characters as we can until the completions start to differ */ lastchar = c; - int i,j,k=0; + int i, j, k = 0; char *tptr = comphelp.entries[0]; - + /* quickly try to see where our entries differ * corner cases * - if all entries are optional, don't show - * *any* options unless user has provided a letter. - * - if any entry starts with '<' then don't fill in + * *any* options unless user has provided a letter. + * - if any entry starts with '<' then don't fill in * anything. */ - + // skip a leading '[' k = strlen(tptr); - if (*tptr == '[') tptr++; - else if (*tptr == '<') k=0; - - for (i = 1; k!=0 && i < comphelp.num_entries; i++) { - char *wptr=comphelp.entries[i]; - - if (*wptr=='[') wptr++; - else if (*wptr=='<') k=0; - - for (j=0; (j 0; l--, cursor--) { - if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l-1]==',')) break; + if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); } - strncpy((cmd + l), tptr,k); + strncpy((cmd + l), tptr, k); l += k; cursor = l; _write(sockfd, tptr, k); - } else { + } else { _write(sockfd, "\a", 1); } } @@ -1356,10 +1364,10 @@ int cli_loop(struct cli_def *cli, int sockfd) { } /* '?' at end of line - generate applicable 'help' messages */ - if ((c == '?') && (cursor == l) ) { + if ((c == '?') && (cursor == l)) { struct cli_comphelp comphelp = {0}; int i; - int show_cr=1; + int show_cr = 1; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; @@ -1368,20 +1376,19 @@ int cli_loop(struct cli_def *cli, int sockfd) { cli_get_completions(cli, cmd, c, &comphelp); if (comphelp.num_entries == 0) { _write(sockfd, "\a", 1); - } - else if (comphelp.num_entries > 0) { + } else if (comphelp.num_entries > 0) { cli->showprompt = 1; _write(sockfd, "\r\n", 2); - for (i=0;i<(int)comphelp.num_entries;i++) { - if (comphelp.entries[i][2]!='[') show_cr=0; - cli_error(cli, "%s",comphelp.entries[i]); + for (i = 0; i < (int)comphelp.num_entries; i++) { + if (comphelp.entries[i][2] != '[') show_cr = 0; + cli_error(cli, "%s", comphelp.entries[i]); } - if (show_cr) cli_error(cli," "); + if (show_cr) cli_error(cli, " "); } - + cli_free_comphelp(&comphelp); - - if ( comphelp.num_entries>=0) continue; + + if (comphelp.num_entries >= 0) continue; } /* history */ @@ -1595,16 +1602,16 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (l == 0) continue; if (cmd[l - 1] != '?' && strcasecmp(cmd, "history") != 0) cli_add_history(cli, cmd); - rc=cli_run_command(cli, cmd); + rc = cli_run_command(cli, cmd); switch (rc) { case CLI_BUILDMODE_COMMAND_ERROR: // unable to enter buildmode successfully - cli_print(cli, "Failure entering build mode for '%s'" , cli->buildmode->cname); + cli_print(cli, "Failure entering build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); continue; case CLI_BUILDMODE_COMMAND_CANCEL: // called if user enters 'cancel' - cli_print(cli, "Canceling build mode for '%s'" , cli->buildmode->cname); + cli_print(cli, "Canceling build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); break; case CLI_BUILDMODE_COMMAND_EXIT: @@ -1619,9 +1626,9 @@ int cli_loop(struct cli_def *cli, int sockfd) { default: break; } - - // Process is done if we get a CLI_QUIT, - if (rc==CLI_QUIT) break; + + // Process is done if we get a CLI_QUIT, + if (rc == CLI_QUIT) break; } // Update the last_action time now as the last command run could take a @@ -1724,9 +1731,7 @@ void cli_bufprint(struct cli_def *cli, const char *format, ...) { va_end(ap); } -void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) { - _print(cli, PRINT_BUFFERED, format, ap); -} +void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) { _print(cli, PRINT_BUFFERED, format, ap); } void cli_print(struct cli_def *cli, const char *format, ...) { va_list ap; @@ -1767,7 +1772,7 @@ int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli filt->filter = cli_match_filter; filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1); if (!state) return CLI_ERROR; - + if (argv[0][0] == 'i' || (argv[0][0] == 'e' && argv[0][1] == 'x')) // include/exclude { if (argv[0][0] == 'e') state->flags = MATCH_INVERT; @@ -1992,26 +1997,19 @@ void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, in cli->idle_timeout_callback = callback; } -void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) { - cli->telnet_protocol = !!telnet_protocol; -} - -void cli_set_context(struct cli_def *cli, void *context) { - cli->user_context = context; -} +void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) { cli->telnet_protocol = !!telnet_protocol; } -void *cli_get_context(struct cli_def *cli) { - return cli->user_context; -} +void cli_set_context(struct cli_def *cli, void *context) { cli->user_context = context; } +void *cli_get_context(struct cli_def *cli) { return cli->user_context; } -/* cli_register_filter/cli_unregister_filter are limited coies of cli_register_command/cli_unregister_command. +/* cli_register_filter/cli_unregister_filter are limited coies of cli_register_command/cli_unregister_command. * Filters do not have a hierarchy, so they are all siblings of each other so we only need the 'next' field. */ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, - int(*init) (struct cli_def *cli, int, char **, struct cli_filter *), - int(*filter)(struct cli_def *, const char *, void *), - int privilege, int mode, const char *help) { + int (*init)(struct cli_def *cli, int, char **, struct cli_filter *), + int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode, + const char *help) { struct cli_command *c, *p; if (!command) return NULL; @@ -2034,7 +2032,7 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command } if (!cli->filter_commands) { - cli->filter_commands = c; + cli->filter_commands = c; } else { for (p = cli->filter_commands; p && p->next; p = p->next) ; @@ -2043,7 +2041,6 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command return c; } - int cli_unregister_filter(struct cli_def *cli, const char *command) { struct cli_command *c, *p = NULL; @@ -2064,30 +2061,29 @@ int cli_unregister_filter(struct cli_def *cli, const char *command) { return CLI_OK; } - void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) { struct cli_optarg_pair *c; - if (!optarg_pair || !*optarg_pair) return ; - - for (c=*optarg_pair;c;) { - *optarg_pair=c->next; + if (!optarg_pair || !*optarg_pair) return; + + for (c = *optarg_pair; c;) { + *optarg_pair = c->next; free_z(c->name); free_z(c->value); free_z(c); - c=*optarg_pair; + c = *optarg_pair; } } char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) { - char *value=NULL; + char *value = NULL; struct cli_optarg_pair *optarg_pair; if (!name || !cli->found_optargs) return NULL; - - for (optarg_pair = cli->found_optargs; optarg_pair && !value ; optarg_pair=optarg_pair->next) { - if (strcmp(optarg_pair->name, name)==0) { - if (find_after && (find_after==optarg_pair->value)) { - find_after=NULL; + + for (optarg_pair = cli->found_optargs; optarg_pair && !value; optarg_pair = optarg_pair->next) { + if (strcmp(optarg_pair->name, name) == 0) { + if (find_after && (find_after == optarg_pair->value)) { + find_after = NULL; continue; } value = optarg_pair->value; @@ -2100,14 +2096,14 @@ static void cli_optarg_build_shortest(struct cli_optarg *optarg) { struct cli_optarg *c, *p; char *cp, *pp; unsigned int len; - + for (c = optarg; c; c = c->next) { c->unique_len = 1; - for (p=optarg; p; p=p->next) { - if (c==p) continue; + for (p = optarg; p; p = p->next) { + if (c == p) continue; cp = c->name; pp = p->name; - len=1; + len = 1; while (*cp && *pp && *cp++ == *pp++) len++; if (len > c->unique_len) c->unique_len = len; } @@ -2117,26 +2113,25 @@ static void cli_optarg_build_shortest(struct cli_optarg *optarg) { void cli_free_optarg(struct cli_optarg *optarg) { free_z(optarg->help); free_z(optarg->name); - free_z(optarg); + free_z(optarg); } - int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int privilege, int mode, const char *help, - int (*get_completions)(struct cli_def *cli, const char *, const char *, struct cli_comphelp * ), + int (*get_completions)(struct cli_def *cli, const char *, const char *, struct cli_comphelp *), int (*validator)(struct cli_def *cli, const char *, const char *), int (*transient_mode)(struct cli_def *cli, const char *, const char *)) { struct cli_optarg *optarg; - struct cli_optarg *lastopt=NULL; - struct cli_optarg *ptr=NULL; + struct cli_optarg *lastopt = NULL; + struct cli_optarg *ptr = NULL; int retval = CLI_ERROR; - + // name must not already exist with this priv/mode - for (ptr=cmd->optargs,lastopt=NULL; ptr ;lastopt=ptr, ptr=ptr->next) { + for (ptr = cmd->optargs, lastopt = NULL; ptr; lastopt = ptr, ptr = ptr->next) { if (!(strcmp(name, ptr->name)) && (ptr->mode == mode) && (ptr->privilege == privilege)) { return CLI_ERROR; } } - if (!(optarg = calloc(sizeof(struct cli_optarg),1))) goto CLEANUP; + if (!(optarg = calloc(sizeof(struct cli_optarg), 1))) goto CLEANUP; if (!(optarg->name = strdup(name))) goto CLEANUP; if (help && !(optarg->help = strdup(help))) goto CLEANUP; @@ -2147,11 +2142,13 @@ int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, in optarg->transient_mode = transient_mode; optarg->flags = flags; - if (lastopt) lastopt->next = optarg; - else cmd->optargs = optarg; + if (lastopt) + lastopt->next = optarg; + else + cmd->optargs = optarg; cli_optarg_build_shortest(cmd->optargs); retval = CLI_OK; - + CLEANUP: if (retval != CLI_OK) { cli_free_optarg(optarg); @@ -2164,44 +2161,42 @@ int cli_unregister_optarg(struct cli_command *cmd, const char *name) { struct cli_optarg *lastptr; int retval = CLI_ERROR; // iterate looking for this option name, stoping at end or if name matches - for (lastptr=NULL, ptr=cmd->optargs; ptr && strcmp(ptr->name, name); lastptr=ptr, ptr=ptr->next) - { + for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next) { ; } - + // if ptr, then we found the optarg to delete if (ptr) { if (lastptr) { // not first optarg lastptr->next = ptr->next; - ptr->next=NULL; - } - else { + ptr->next = NULL; + } else { // first optarg cmd->optargs = ptr->next; - ptr->next=NULL; + ptr->next = NULL; } cli_free_optarg(ptr); cli_optarg_build_shortest(cmd->optargs); - retval = CLI_OK; - } + retval = CLI_OK; + } return retval; -} +} void cli_unregister_all_optarg(struct cli_optarg *optarg) { struct cli_optarg *p; - - for (;optarg; optarg=p) { - p=optarg->next; - cli_free_optarg(optarg); + + for (; optarg; optarg = p) { + p = optarg->next; + cli_free_optarg(optarg); } } void cli_int_unset_optarg_value(struct cli_def *cli, const char *name) { - struct cli_optarg_pair **p,*c; - for (p=&cli->found_optargs, c=*p ; *p; ) { - c=*p; - + struct cli_optarg_pair **p, *c; + for (p = &cli->found_optargs, c = *p; *p;) { + c = *p; + if (!strcmp(c->name, name)) { *p = c->next; free_z(c->name); @@ -2216,8 +2211,9 @@ void cli_int_unset_optarg_value(struct cli_def *cli, const char *name) { int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple) { struct cli_optarg_pair *optarg_pair, **anchor; int rc = CLI_ERROR; - - for (optarg_pair=cli->found_optargs, anchor=&cli->found_optargs; optarg_pair ; anchor = &optarg_pair->next, optarg_pair=optarg_pair->next) { + + for (optarg_pair = cli->found_optargs, anchor = &cli->found_optargs; optarg_pair; + anchor = &optarg_pair->next, optarg_pair = optarg_pair->next) { // break if we found this name *and* allow_multiple is false if (!strcmp(optarg_pair->name, name) && !allow_multiple) { break; @@ -2225,45 +2221,44 @@ int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char * } // if we *didn't* find this, then allocate a new entry before proceeding if (!optarg_pair) { - optarg_pair = (struct cli_optarg_pair*)calloc(1,sizeof(struct cli_optarg_pair)); - *anchor = optarg_pair; + optarg_pair = (struct cli_optarg_pair *)calloc(1, sizeof(struct cli_optarg_pair)); + *anchor = optarg_pair; } // set the value if (optarg_pair) { - // name is null only if we didn't find it - if (!optarg_pair->name) optarg_pair->name=strdup(name); + // name is null only if we didn't find it + if (!optarg_pair->name) optarg_pair->name = strdup(name); - // value may be overwritten, so free any old value. - if ( optarg_pair->value) free_z(optarg_pair->value); - optarg_pair->value=strdup(value); - - rc=CLI_OK; + // value may be overwritten, so free any old value. + if (optarg_pair->value) free_z(optarg_pair->value); + optarg_pair->value = strdup(value); + + rc = CLI_OK; } return rc; } -struct cli_optarg_pair * cli_get_all_found_optargs(struct cli_def *cli) { - if (cli ) return cli->found_optargs; +struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli) { + if (cli) return cli->found_optargs; return NULL; } - -char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after) { - char *value=NULL; +char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after) { + char *value = NULL; struct cli_optarg_pair *optarg_pair; - - printf("cli_get_optarg_value entry - looking for <%s> after <%p>\n", name, (void*)find_after); - for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair=optarg_pair->next) { - printf(" Checking %s with value %s <%p> \n", optarg_pair->name, optarg_pair->value, (void*)optarg_pair->value); - + + printf("cli_get_optarg_value entry - looking for <%s> after <%p>\n", name, (void *)find_after); + for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair = optarg_pair->next) { + printf(" Checking %s with value %s <%p> \n", optarg_pair->name, optarg_pair->value, (void *)optarg_pair->value); + // check next entry if this isn't our name if (strcasecmp(optarg_pair->name, name)) continue; // did we have a find_after, then ignore anything up until our find_after match - if ((find_after )&& (optarg_pair->value == find_after)) { - find_after=NULL; + if ((find_after) && (optarg_pair->value == find_after)) { + find_after = NULL; continue; - } else if ( !find_after) { + } else if (!find_after) { value = optarg_pair->value; } } @@ -2271,9 +2266,7 @@ char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_af return value; } - - -void cli_int_free_buildmode(struct cli_def *cli) { +void cli_int_free_buildmode(struct cli_def *cli) { if (!cli || !cli->buildmode) return; if (cli->buildmode->commands) cli_unregister_all(cli, cli->buildmode->commands); cli->mode = cli->buildmode->mode; @@ -2286,16 +2279,15 @@ void cli_int_free_buildmode(struct cli_def *cli) { int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text) { struct cli_optarg *optarg; struct cli_command *c; - struct cli_buildmode *buildmode ; - + struct cli_buildmode *buildmode; - if ( (!cli ) || !(buildmode=(struct cli_buildmode *)calloc(1,sizeof(struct cli_buildmode)))) { + if ((!cli) || !(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) { cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command); return CLI_BUILDMODE_COMMAND_ERROR; } - + // clean up any shrapnel from earlier - shouldn't be any but.... - if ( cli->buildmode) { + if (cli->buildmode) { cli_int_free_buildmode(cli); } @@ -2307,43 +2299,53 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag // need this to verify we have all *required* arguments cli->buildmode->c = stage->command; - + // build new *limited* list of commands from this commands optargs - for (optarg = stage->command->optargs; optarg ; optarg=optarg->next) { + for (optarg = stage->command->optargs; optarg; optarg = optarg->next) { // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel - if (!strcmp(optarg->name,"cancel") || (!strcmp(optarg->name, "exit"))) { + if (!strcmp(optarg->name, "cancel") || (!strcmp(optarg->name, "exit"))) { cli_error(cli, "Unable to build buildmode mode from optarg named %s", optarg->name); return CLI_BUILDMODE_COMMAND_ERROR; } - if (optarg->flags&(CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE)) continue; - if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) continue; - else if (optarg->flags & ( CLI_CMD_OPTIONAL_ARGUMENT|CLI_CMD_ARGUMENT)) { - if ((c=cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_cmd_cback, optarg->privilege, cli->mode, optarg->help))) { - cli_register_optarg(c, optarg->name, CLI_CMD_ARGUMENT|(optarg->flags&CLI_CMD_OPTION_MULTIPLE), optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, NULL); - } - else { + if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE)) continue; + if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) + continue; + else if (optarg->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_ARGUMENT)) { + if ((c = cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_cmd_cback, + optarg->privilege, cli->mode, optarg->help))) { + cli_register_optarg(c, optarg->name, CLI_CMD_ARGUMENT | (optarg->flags & CLI_CMD_OPTION_MULTIPLE), + optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, + NULL); + } else { return CLI_BUILDMODE_COMMAND_ERROR; } } else { - if (optarg->flags&CLI_CMD_OPTION_MULTIPLE) { - if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, optarg->privilege, cli->mode, optarg->help)) { + if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) { + if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, + optarg->privilege, cli->mode, optarg->help)) { return CLI_BUILDMODE_COMMAND_ERROR; } } else { - if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->privilege, cli->mode, optarg->help)) { + if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, + optarg->privilege, cli->mode, optarg->help)) { return CLI_BUILDMODE_COMMAND_ERROR; - } + } } } } cli->buildmode->cname = strdup(cli_command_name(cli, stage->command)); // and lastly two 'always there' commands to cancel current mode and to execute the command - cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); - cli_int_register_buildmode_command(cli, NULL, "exit", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Exit and execute command"); - cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Show current settings"); - c=cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Unset a setting"); - cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT|CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode, "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); - + cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, PRIVILEGE_UNPRIVILEGED, + cli->mode, "Cancel command"); + cli_int_register_buildmode_command(cli, NULL, "exit", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, + "Exit and execute command"); + cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, + "Show current settings"); + c = cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, PRIVILEGE_UNPRIVILEGED, + cli->mode, "Unset a setting"); + cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT | CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode, + "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); + cli_build_shortest(cli, cli->buildmode->commands); return CLI_BUILDMODE_COMMAND_START; @@ -2371,11 +2373,12 @@ int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *comman return CLI_OK; } -// a copy of cli_register_command, but using cli->buildmode_cmd rather than cli->commands as the anchor +// a copy of cli_register_command, but using cli->buildmode_cmd rather than cli->commands as the anchor -struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, - int (*callback)(struct cli_def *cli, const char *, char **, int), - int privilege, int mode, const char *help) { +struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, + const char *command, + int (*callback)(struct cli_def *cli, const char *, char **, int), + int privilege, int mode, const char *help) { struct cli_command *c, *p; if (!command) return NULL; @@ -2418,85 +2421,83 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru } int cli_int_execute_buildmode(struct cli_def *cli) { - struct cli_optarg *optarg=NULL; + struct cli_optarg *optarg = NULL; int rc = CLI_OK; char *cmdline; - - char *value=NULL; - - cmdline = strdup(cli_command_name(cli,cli->buildmode->c)); - for (optarg=cli->buildmode->c->optargs; rc==CLI_OK && optarg;optarg=optarg->next) { - value=NULL; + + char *value = NULL; + + cmdline = strdup(cli_command_name(cli, cli->buildmode->c)); + for (optarg = cli->buildmode->c->optargs; rc == CLI_OK && optarg; optarg = optarg->next) { + value = NULL; do { if (cli->privilege < optarg->privilege) continue; - if ((optarg->mode!=cli->buildmode->mode) && (optarg->mode != cli->buildmode->transient_mode) && (optarg->mode != MODE_ANY)) continue; - - value = cli_get_optarg_value(cli,optarg->name,value); + if ((optarg->mode != cli->buildmode->mode) && (optarg->mode != cli->buildmode->transient_mode) && + (optarg->mode != MODE_ANY)) + continue; + + value = cli_get_optarg_value(cli, optarg->name, value); if (!value && optarg->flags & CLI_CMD_ARGUMENT) { - cli_error(cli,"Missing required argument %s", optarg->name); - rc=CLI_MISSING_ARGUMENT; - } - else if (value) { + cli_error(cli, "Missing required argument %s", optarg->name); + rc = CLI_MISSING_ARGUMENT; + } else if (value) { if (optarg->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) { - if (!(cmdline=cli_int_buildmode_extend_cmdline(cmdline,value))) { - cli_error(cli,"Unable to append to building commandlne"); - rc=CLI_ERROR; + if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, value))) { + cli_error(cli, "Unable to append to building commandlne"); + rc = CLI_ERROR; } } else { - if (!(cmdline=cli_int_buildmode_extend_cmdline(cmdline,optarg->name))) { - cli_error(cli,"Unable to append to building commandlne"); - rc=CLI_ERROR; + if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, optarg->name))) { + cli_error(cli, "Unable to append to building commandlne"); + rc = CLI_ERROR; } - if (!(cmdline=cli_int_buildmode_extend_cmdline(cmdline,value))) { - cli_error(cli,"Unable to append to building commandlne"); - rc=CLI_ERROR; + if (!(cmdline = cli_int_buildmode_extend_cmdline(cmdline, value))) { + cli_error(cli, "Unable to append to building commandlne"); + rc = CLI_ERROR; } } } - } while (rc==CLI_OK && value && optarg->flags & CLI_CMD_OPTION_MULTIPLE); + } while (rc == CLI_OK && value && optarg->flags & CLI_CMD_OPTION_MULTIPLE); } - - if (rc==CLI_OK) { + + if (rc == CLI_OK) { cli_int_free_buildmode(cli); cli_add_history(cli, cmdline); - rc = cli_run_command(cli, cmdline); + rc = cli_run_command(cli, cmdline); } free_z(cmdline); cli_int_free_buildmode(cli); return rc; } +char *cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { + char *tptr = NULL; + char *cptr = NULL; + size_t oldlen = strlen(cmdline); + size_t wordlen = strlen(word); + int add_quotes = 0; - -char * cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { - char *tptr=NULL; - char *cptr=NULL; - size_t oldlen=strlen(cmdline); - size_t wordlen=strlen(word); - int add_quotes=0; - /* * Allocate enough space to hold the old string, a space, and the new string (including null terminator). * Also include enough space for a quote around the string if it contains a whitespace character */ - if ( (tptr=(char*)realloc(cmdline, oldlen+1+wordlen+1+2)) ) { - strcat(tptr," "); - for (cptr=word;*cptr;cptr++) { + if ((tptr = (char *)realloc(cmdline, oldlen + 1 + wordlen + 1 + 2))) { + strcat(tptr, " "); + for (cptr = word; *cptr; cptr++) { if (isspace(*cptr)) { - add_quotes=1; + add_quotes = 1; break; } - } - if (add_quotes) strcat(tptr,"'"); - strcat(tptr,word); - if (add_quotes) strcat(tptr,"'"); + } + if (add_quotes) strcat(tptr, "'"); + strcat(tptr, word); + if (add_quotes) strcat(tptr, "'"); } return tptr; } - int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc=CLI_BUILDMODE_COMMAND_EXTEND; + int rc = CLI_BUILDMODE_COMMAND_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); @@ -2507,14 +2508,14 @@ int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char * // a 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc=CLI_BUILDMODE_COMMAND_EXTEND; + int rc = CLI_BUILDMODE_COMMAND_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } if (cli_int_add_optarg_value(cli, command, command, 0)) { - cli_error(cli,"Problem setting value for optional flag %s", command); + cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } return rc; @@ -2522,48 +2523,47 @@ int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char // a 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc=CLI_BUILDMODE_COMMAND_EXTEND; + int rc = CLI_BUILDMODE_COMMAND_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } if (cli_int_add_optarg_value(cli, command, command, CLI_CMD_OPTION_MULTIPLE)) { - cli_error(cli,"Problem setting value for optional flag %s", command); + cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } - + return rc; } int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc= CLI_BUILDMODE_COMMAND_CANCEL; + int rc = CLI_BUILDMODE_COMMAND_CANCEL; - if (argc>0) { + if (argc > 0) { cli_error(cli, "Extra arguments on command line, command ignored."); - rc=CLI_ERROR; + rc = CLI_ERROR; } return rc; } int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc= CLI_BUILDMODE_COMMAND_EXIT; + int rc = CLI_BUILDMODE_COMMAND_EXIT; - if (argc>0) { + if (argc > 0) { cli_error(cli, "Extra arguments on command line, command ignored."); - rc=CLI_ERROR; + rc = CLI_ERROR; } return rc; } int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - struct cli_optarg_pair *optarg_pair; - if (cli && cli->buildmode ){ - for (optarg_pair=cli->found_optargs; optarg_pair; optarg_pair=optarg_pair->next) - { + struct cli_optarg_pair *optarg_pair; + if (cli && cli->buildmode) { + for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { // only show vars that are also current 'commands' struct cli_command *c = cli->buildmode->commands; - for (;c ; c=c->next) { + for (; c; c = c->next) { if (!strcmp(c->command, optarg_pair->name)) { cli_print(cli, " %-20s = %s", optarg_pair->name, optarg_pair->value); break; @@ -2576,48 +2576,44 @@ int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { // iterate over our 'set' variables to see if that variable is also a 'valid' command right now - + struct cli_command *c; - + // is this 'optarg' to remove one of the current commands? - for (c=cli->buildmode->commands; c; c=c->next) { + for (c = cli->buildmode->commands; c; c = c->next) { if (cli->privilege < c->privilege) continue; - if ((cli->buildmode->mode!=c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY)) continue; + if ((cli->buildmode->mode != c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY)) + continue; if (strcmp(c->command, argv[0])) continue; - // Ok, go fry anything by this name - + // Ok, go fry anything by this name + cli_int_unset_optarg_value(cli, argv[0]); break; } - - return CLI_OK; -} -/* Generate a list of variables that *have* been set */ -int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { return CLI_OK; } -int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { +/* Generate a list of variables that *have* been set */ +int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, + struct cli_comphelp *comphelp) { return CLI_OK; } +int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { return CLI_OK; } -void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { - cli->transient_mode = transient_mode; -} +void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { cli->transient_mode = transient_mode; } int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry) { int retval = CLI_ERROR; if (comphelp && entry) { char *dupelement = strdup(entry); - char **duparray = (char **)realloc((void*)comphelp->entries, sizeof(char *)*(comphelp->num_entries+1)); + char **duparray = (char **)realloc((void *)comphelp->entries, sizeof(char *) * (comphelp->num_entries + 1)); if (dupelement && duparray) { - comphelp->entries=duparray; + comphelp->entries = duparray; comphelp->entries[comphelp->num_entries++] = dupelement; retval = CLI_OK; - } - else { + } else { free_z(dupelement); free_z(duparray); } @@ -2628,14 +2624,14 @@ int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry) { void cli_free_comphelp(struct cli_comphelp *comphelp) { if (comphelp) { int idx; - - for (idx=0;idxnum_entries;idx++) free_z(comphelp->entries[idx]); + + for (idx = 0; idx < comphelp->num_entries; idx++) free_z(comphelp->entries[idx]); free_z(comphelp->entries); } } -static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, - int start_word, struct cli_pipeline_stage *stage) { +static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, int start_word, + struct cli_pipeline_stage *stage) { struct cli_command *c, *again_config = NULL, *again_any = NULL; int c_words = stage->num_words; @@ -2681,11 +2677,10 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma return CLI_ERROR; } - CORRECT_CHECKS: if (rc == CLI_OK) { - + stage->command = c; stage->first_unmatched = start_word + 1; stage->first_optarg = stage->first_unmatched; @@ -2723,72 +2718,73 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { int i; - int rc=CLI_OK; + int rc = CLI_OK; - struct cli_command *first_command=NULL; - cli->found_optargs=NULL; + struct cli_command *first_command = NULL; + cli->found_optargs = NULL; if (cli->buildmode && cli->buildmode->commands) { first_command = cli->buildmode->commands; } else { first_command = cli->commands; } - for (i=0;inum_stages;i++) { + for (i = 0; i < pipeline->num_stages; i++) { // in 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs - if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; - else cli->found_optargs = NULL; - rc=cli_int_locate_command(cli, (i==0)?first_command:cli->filter_commands, 0, &pipeline->stage[i]); - + if (cli->buildmode) + cli->found_optargs = cli->buildmode->found_optargs; + else + cli->found_optargs = NULL; + rc = cli_int_locate_command(cli, (i == 0) ? first_command : cli->filter_commands, 0, &pipeline->stage[i]); + // and save our found optargs for later use - if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; - else pipeline->stage[i].found_optargs = cli->found_optargs; + if (cli->buildmode) + cli->buildmode->found_optargs = cli->found_optargs; + else + pipeline->stage[i].found_optargs = cli->found_optargs; - if (rc!=CLI_OK) break; + if (rc != CLI_OK) break; } return rc; } - - struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline) { int i; if (pipeline) { - for (i=0;inum_stages;i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs); - for (i=0;inum_words;i++) free_z(pipeline->words[i]); + for (i = 0; i < pipeline->num_stages; i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs); + for (i = 0; i < pipeline->num_words; i++) free_z(pipeline->words[i]); free_z(pipeline->cmdline); free_z(pipeline); - pipeline=NULL; + pipeline = NULL; } return pipeline; } void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { - int i,j; - struct cli_pipeline_stage* stage; + int i, j; + struct cli_pipeline_stage *stage; char **word; struct cli_optarg_pair *optarg_pair; - - for (i=0,word=pipeline->words;inum_words;i++,word++) printf("[%s] ",*word); + + for (i = 0, word = pipeline->words; i < pipeline->num_words; i++, word++) printf("[%s] ", *word); printf("\n"); - printf( "#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words); - - for (i=0;inum_stages;i++) { - stage=&(pipeline->stage[i]); - printf( " #%d(%d words) first_unmatched=%d: " , i, stage->num_words, stage->first_unmatched); - for (j=0; jnum_words;j++) { - printf(" [%s]",stage->words[j]); + printf("#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words); + + for (i = 0; i < pipeline->num_stages; i++) { + stage = &(pipeline->stage[i]); + printf(" #%d(%d words) first_unmatched=%d: ", i, stage->num_words, stage->first_unmatched); + for (j = 0; j < stage->num_words; j++) { + printf(" [%s]", stage->words[j]); } printf("\n"); if (stage->command) { printf(" Command: %s\n", stage->command->command); } - for (optarg_pair = stage->found_optargs; optarg_pair; optarg_pair=optarg_pair->next) { + for (optarg_pair = stage->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { printf(" %s: %s\n", optarg_pair->name, optarg_pair->value); } - - } + } } /* @@ -2799,25 +2795,25 @@ struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char * int i; struct cli_pipeline_stage *stage; char **word; - struct cli_pipeline *pipeline=NULL; - - cli->found_optargs=NULL; + struct cli_pipeline *pipeline = NULL; + + cli->found_optargs = NULL; if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; if (!command) return NULL; while (*command && isspace(*command)) command++; - + if (!(pipeline = (struct cli_pipeline *)calloc(1, sizeof(struct cli_pipeline)))) return NULL; - pipeline->cmdline=(char*)strdup(command); - + pipeline->cmdline = (char *)strdup(command); + pipeline->num_words = cli_parse_line(command, pipeline->words, CLI_MAX_LINE_WORDS); - pipeline->stage[0].num_words=0; - stage=&pipeline->stage[0]; + pipeline->stage[0].num_words = 0; + stage = &pipeline->stage[0]; word = pipeline->words; stage->words = word; - for (i=0 ; i < pipeline->num_words ; i++, word++) { + for (i = 0; i < pipeline->num_words; i++, word++) { if (*word[0] == '|') { - if (cli->buildmode ) { + if (cli->buildmode) { // can't allow filters in buildmode commands cli_int_free_pipeline(pipeline); return NULL; @@ -2826,88 +2822,90 @@ struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char * stage++; stage->num_words = 0; pipeline->num_stages++; - stage->words = word+1; // first word of the next stage is one past where we are (possibly NULL) - } - else - { + stage->words = word + 1; // first word of the next stage is one past where we are (possibly NULL) + } else { stage->num_words++; } } - stage->stage_num=pipeline->num_stages; + stage->stage_num = pipeline->num_stages; pipeline->num_stages++; return pipeline; } -int cli_int_execute_pipeline (struct cli_def *cli, struct cli_pipeline *pipeline) { +int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { int stage_num; - int rc=CLI_OK; + int rc = CLI_OK; struct cli_filter **filt = &cli->filters; if (pipeline) { - for (stage_num=1;stage_numnum_stages;stage_num++) { - struct cli_pipeline_stage *stage=&pipeline->stage[stage_num]; + for (stage_num = 1; stage_num < pipeline->num_stages; stage_num++) { + struct cli_pipeline_stage *stage = &pipeline->stage[stage_num]; cli->found_optargs = stage->found_optargs; - *filt=calloc(sizeof(struct cli_filter), 1); + *filt = calloc(sizeof(struct cli_filter), 1); if (*filt) { - if ((rc=stage->command->init(cli, stage->num_words, stage->words, *filt)!=CLI_OK)) { + if ((rc = stage->command->init(cli, stage->num_words, stage->words, *filt) != CLI_OK)) { break; } - filt = &(*filt)->next; + filt = &(*filt)->next; } } - - // Did everything init? If so, execute, otherwise skip execution - if ( (rc==CLI_OK) && pipeline->stage[0].command->callback) { - struct cli_pipeline_stage *stage=&pipeline->stage[0]; - - if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; - else cli->found_optargs = pipeline->stage[0].found_optargs; - rc=stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words+stage->first_unmatched, stage->num_words - stage->first_unmatched); - if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs ; + + // Did everything init? If so, execute, otherwise skip execution + if ((rc == CLI_OK) && pipeline->stage[0].command->callback) { + struct cli_pipeline_stage *stage = &pipeline->stage[0]; + + if (cli->buildmode) + cli->found_optargs = cli->buildmode->found_optargs; + else + cli->found_optargs = pipeline->stage[0].found_optargs; + rc = stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words + stage->first_unmatched, + stage->num_words - stage->first_unmatched); + if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; } } - // Now teardown any filters + // Now teardown any filters while (cli->filters) { struct cli_filter *filt = cli->filters; if (filt->filter) filt->filter(cli, NULL, cli->filters->data); - cli->filters=filt->next; + cli->filters = filt->next; free_z(filt); } - cli->found_optargs=NULL; + cli->found_optargs = NULL; return rc; } -static char DELIM_OPT_START[]="["; -static char DELIM_OPT_END[]="]"; -static char DELIM_ARG_START[]="<"; -static char DELIM_ARG_END[]=">"; -static char DELIM_NONE[]=""; +static char DELIM_OPT_START[] = "["; +static char DELIM_OPT_END[] = "]"; +static char DELIM_ARG_START[] = "<"; +static char DELIM_ARG_END[] = ">"; +static char DELIM_NONE[] = ""; static char BUILDMODE_YES[] = " (enter buildmode)"; static char BUILDMODE_NO[] = ""; -static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *optarg, struct cli_comphelp *comphelp, - int num_candidates, const char lastchar, const char *anchor_word, const char *next_word) { - int help_insert=0; - char *delim_start=DELIM_NONE; - - char *delim_end=DELIM_NONE; - char *allow_buildmode=BUILDMODE_NO; +static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *optarg, struct cli_comphelp *comphelp, + int num_candidates, const char lastchar, const char *anchor_word, + const char *next_word) { + int help_insert = 0; + char *delim_start = DELIM_NONE; + + char *delim_end = DELIM_NONE; + char *allow_buildmode = BUILDMODE_NO; int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL; - char *tptr=NULL; - + char *tptr = NULL; + // if we've already seen a value by this exact name, skip it, unless the multiple flag is set if (cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) return; get_completions = optarg->get_completions; if (optarg->flags & CLI_CMD_OPTIONAL_FLAG) { - if (!( anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { - delim_start=DELIM_OPT_START; - delim_end=DELIM_OPT_END; - get_completions = NULL; // no point, completor of field is the name itself + if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { + delim_start = DELIM_OPT_START; + delim_end = DELIM_OPT_END; + get_completions = NULL; // no point, completor of field is the name itself } } else if (optarg->flags & CLI_CMD_ARGUMENT) { - delim_start=DELIM_ARG_START; - delim_end=DELIM_ARG_END; + delim_start = DELIM_ARG_START; + delim_end = DELIM_ARG_END; } else if (optarg->flags & CLI_CMD_OPTIONAL_ARGUMENT) { /* * optional args can match against the name the value. @@ -2915,102 +2913,103 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta * So if anchor_word==next_word we're looking at the 'name' of the optarg, otherwise we * know the name and are going against the value. */ - if (anchor_word!=next_word) { - // matching against optional argument 'value' + if (anchor_word != next_word) { + // matching against optional argument 'value' help_insert = 0; if (!get_completions) { - delim_start=DELIM_ARG_START; - delim_end=DELIM_ARG_END; + delim_start = DELIM_ARG_START; + delim_end = DELIM_ARG_END; } } else { // matching against optional argument 'name' help_insert = 1; - get_completions=NULL; // matching against the name, not the following field value - if (!( anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { - delim_start=DELIM_OPT_START; - delim_end=DELIM_OPT_END; + get_completions = NULL; // matching against the name, not the following field value + if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { + delim_start = DELIM_OPT_START; + delim_end = DELIM_OPT_END; } } } // Fill in with help text or completor value(s) as indicated - if ((lastchar == '?') && (asprintf(&tptr, "%s%s%s", delim_start,optarg->name,delim_end) != -1)) { - if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) allow_buildmode=BUILDMODE_YES; - if (help_insert && (asprintf(&tptr, " %-20s enter '%s' to %s%s", tptr, optarg->name, (optarg->help)?optarg->help : "",allow_buildmode)!=-1)) { + if ((lastchar == '?') && (asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1)) { + if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) allow_buildmode = BUILDMODE_YES; + if (help_insert && (asprintf(&tptr, " %-20s enter '%s' to %s%s", tptr, optarg->name, + (optarg->help) ? optarg->help : "", allow_buildmode) != -1)) { cli_add_comphelp_entry(comphelp, tptr); - free_z(tptr); - } - else if (asprintf(&tptr, " %-20s %s%s", tptr, (optarg->help)?optarg->help : "",allow_buildmode)!=-1) { + free_z(tptr); + } else if (asprintf(&tptr, " %-20s %s%s", tptr, (optarg->help) ? optarg->help : "", allow_buildmode) != -1) { cli_add_comphelp_entry(comphelp, tptr); - free_z(tptr); - } + free_z(tptr); + } } else if (lastchar == CTRL('I')) { if (get_completions) { - (*get_completions)(cli, optarg->name, next_word, comphelp); - } else if ( (!anchor_word || !strncmp(anchor_word, optarg->name, strlen(anchor_word))) && (asprintf(&tptr, "%s%s%s", delim_start,optarg->name,delim_end) != -1)) { - cli_add_comphelp_entry(comphelp, tptr); - free_z(tptr); + (*get_completions)(cli, optarg->name, next_word, comphelp); + } else if ((!anchor_word || !strncmp(anchor_word, optarg->name, strlen(anchor_word))) && + (asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1)) { + cli_add_comphelp_entry(comphelp, tptr); + free_z(tptr); } } } - -static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp) { - struct cli_optarg *optarg=NULL, *oaptr=NULL; - int w_idx; // word_index - int w_incr; // word_increment +static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, + char lastchar, struct cli_comphelp *comphelp) { + struct cli_optarg *optarg = NULL, *oaptr = NULL; + int w_idx; // word_index + int w_incr; // word_increment struct cli_optarg *candidates[CLI_MAX_LINE_WORDS]; - int num_candidates=0; - int c_idx; // candidate_idx + int num_candidates = 0; + int c_idx; // candidate_idx char *value; - int is_last_word=0; - int (*validator)(struct cli_def*,const char *name, const char *value); + int is_last_word = 0; + int (*validator)(struct cli_def *, const char * name, const char * value); /* - * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to + * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to * know what has already been found. There should be no ambiguities before the 'last' word. - * Note specifically that for tab completions and help the *last* word can be a null pointer. + * Note specifically that for tab completions and help the *last* word can be a null pointer. */ - stage->error_word=NULL; + stage->error_word = NULL; /* start our optarg and word pointers at the beginning * optarg will be incremented *only* when an argument is identified * w_idx will be incremented either by 1 (optflag or argument) or 2 (optional argument) */ - w_idx=stage->first_unmatched; - optarg=cmd->optargs; - num_candidates=0; - while (optarg && w_idxnum_words && (num_candidates <=1)) { - num_candidates=0; + w_idx = stage->first_unmatched; + optarg = cmd->optargs; + num_candidates = 0; + while (optarg && w_idx < stage->num_words && (num_candidates <= 1)) { + num_candidates = 0; w_incr = 1; // assume we're only incrementing by a word - if we match an optional argument bump to 2 - + /* the initial loop here is to identify candidates based matching *this* word in order against: * an exact match of the word to the optinal flag/argument name (yield exactly one match and exit the loop) * a partial match for optional flag/argument name * candidate an argument. */ - - for (oaptr=optarg;oaptr;oaptr=oaptr->next) { + + for (oaptr = optarg; oaptr; oaptr = oaptr->next) { // Skip this option unless it matches privileges, MODE_ANY, the current mode, or the transient_mode if (cli->privilege < oaptr->privilege) continue; - if ((oaptr->mode!=cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue; - + if ((oaptr->mode != cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue; /* An exact match for an optional flag/argument name trumps anything and will be the *only* candidate * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a candidate * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are required */ - if (stage->words[w_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG|CLI_CMD_OPTIONAL_ARGUMENT)) && !strcmp(oaptr->name, stage->words[w_idx])) { - candidates[0]=oaptr; - num_candidates=1; + if (stage->words[w_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) && + !strcmp(oaptr->name, stage->words[w_idx])) { + candidates[0] = oaptr; + num_candidates = 1; break; - } else if (!stage->words[w_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || !strncasecmp(oaptr->name, stage->words[w_idx], strlen(stage->words[w_idx])) ) { + } else if (!stage->words[w_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || + !strncasecmp(oaptr->name, stage->words[w_idx], strlen(stage->words[w_idx]))) { candidates[num_candidates++] = oaptr; - } + } if (oaptr->flags & CLI_CMD_ARGUMENT) { break; } - } /* @@ -3019,32 +3018,35 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage * - If we have more than one candidate evaluating for execution punt hard after complaining. * - If we have more than one candidate and we're not at end-of-line ( */ - if (num_candidates==0) break; - if ((num_candidates>1) && ((lastchar=='\0') || (w_idx<(stage->num_words-1)))) { - stage->error_word=stage->words[w_idx]; + if (num_candidates == 0) break; + if ((num_candidates > 1) && ((lastchar == '\0') || (w_idx < (stage->num_words - 1)))) { + stage->error_word = stage->words[w_idx]; stage->status = CLI_AMBIGUOUS; - cli_error(cli,"Ambiguous option/argument for command %s", stage->command->command); + cli_error(cli, "Ambiguous option/argument for command %s", stage->command->command); return; } - + /* - * So now we could have one or more candidates. We need to call get help/completions *only* if this is the 'last-word' + * So now we could have one or more candidates. We need to call get help/completions *only* if this is the + * 'last-word' * Remember that last word for optinal arguments is last or next to last.... */ - if (lastchar!='\0') { - int called_comphelp=0; - for (c_idx=0;c_idxflags&(CLI_CMD_OPTIONAL_FLAG|CLI_CMD_ARGUMENT)) && (w_idx==(stage->num_words-1))) || - ((oaptr->flags&CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx==(stage->num_words-1))) ) { - cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], stage->words[w_idx]); - called_comphelp=1; - } else if ((oaptr->flags&CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx==(stage->num_words-2))) { - cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], stage->words[w_idx+1]); - called_comphelp=1; - } + if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (w_idx == (stage->num_words - 1))) || + ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 1)))) { + cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], + stage->words[w_idx]); + called_comphelp = 1; + } else if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 2))) { + cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], + stage->words[w_idx + 1]); + called_comphelp = 1; + } } // if we were 'end-of-word' and looked for completions/help, return to user if (called_comphelp) { @@ -3052,84 +3054,86 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage return; } } - + // set some values for use later - makes code much easier to read - if ( ((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG|CLI_CMD_ARGUMENT)) && (w_idx==(stage->num_words-1))) || - ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx==(stage->num_words-2))) ) { - is_last_word=1; + if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (w_idx == (stage->num_words - 1))) || + ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 2)))) { + is_last_word = 1; } value = stage->words[w_idx]; oaptr = candidates[0]; validator = oaptr->validator; - + if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT)) { w_incr = 2; - value = stage->words[w_idx+1]; - if (!value && lastchar=='\0') { + value = stage->words[w_idx + 1]; + if (!value && lastchar == '\0') { // hit a optional argument that does not have a value with it - cli_error(cli,"Optional argument %s requires a value", stage->words[w_idx]); - stage->error_word = stage->words[w_idx]; + cli_error(cli, "Optional argument %s requires a value", stage->words[w_idx]); + stage->error_word = stage->words[w_idx]; stage->status = CLI_MISSING_VALUE; - return ; + return; } } /* - * Ok, so we're not at end of string and doing help/completions. - * So see if our value is 'valid', to save it, and see if we have any extra processing to + * Ok, so we're not at end of string and doing help/completions. + * So see if our value is 'valid', to save it, and see if we have any extra processing to * do such as a transient mode check or enter build mode. */ - - if (!validator || ((*validator)(cli, oaptr->name, value)==CLI_OK)) { + + if (!validator || ((*validator)(cli, oaptr->name, value) == CLI_OK)) { if (oaptr->flags & CLI_CMD_DO_NOT_RECORD) { // we want completion and validation, but then leave this 'value' to be seen - used *only* by buildmode // as argv[0] with argc=1 break; } else { // need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing - int set_value_return = 0; - - if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { - char *combined=NULL; - combined = join_words(stage->num_words-w_idx, stage->words + w_idx); - set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); - free_z(combined); - } else { - set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags&CLI_CMD_OPTION_MULTIPLE); - } - - if (set_value_return != CLI_OK) { - cli_error(cli,"%sProblem setting value for command argument %s", lastchar=='\0'?"" : "\n", stage->words[w_idx]); + int set_value_return = 0; + + if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { + char *combined = NULL; + combined = join_words(stage->num_words - w_idx, stage->words + w_idx); + set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); + free_z(combined); + } else { + set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags & CLI_CMD_OPTION_MULTIPLE); + } + + if (set_value_return != CLI_OK) { + cli_error(cli, "%sProblem setting value for command argument %s", lastchar == '\0' ? "" : "\n", + stage->words[w_idx]); cli_reprompt(cli); - stage->error_word=stage->words[w_idx]; + stage->error_word = stage->words[w_idx]; stage->status = CLI_ERROR; return; } - } + } } else { - cli_error(cli,"%sProblem parsing command setting %s with value %s", lastchar=='\0'? "": "\n", oaptr->name, stage->words[w_idx]); + cli_error(cli, "%sProblem parsing command setting %s with value %s", lastchar == '\0' ? "" : "\n", oaptr->name, + stage->words[w_idx]); cli_reprompt(cli); - stage->error_word=stage->words[w_idx]; + stage->error_word = stage->words[w_idx]; stage->status = CLI_ERROR; return; } - - + // if this optarg can set the transient mode, then evaluate it if we're not at last word if (oaptr->transient_mode && (oaptr->transient_mode(cli, oaptr->name, value))) { stage->error_word = stage->words[w_idx]; stage->status = CLI_ERROR; - return ; - } - + return; + } + // only do buildmode optargs if we're a executing a command, parsing command (stage 0), and this is the last word - if ( (stage->status==CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { - stage->status=cli_int_enter_buildmode(cli, stage, value); - return ; + if ((stage->status == CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { + stage->status = cli_int_enter_buildmode(cli, stage, value); + return; } - + /* - * Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg starting point + * Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg + * starting point * we see a true argument */ if (oaptr->flags & CLI_CMD_ARGUMENT) { @@ -3137,25 +3141,22 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage optarg = oaptr->next; } - w_idx+= w_incr; + w_idx += w_incr; stage->first_unmatched = w_idx; - } - - + } + // Ok, if we're evaluating the command for execution, ensure we have all required arguments. if (lastchar == '\0') { - for(; optarg; optarg= optarg->next) { + for (; optarg; optarg = optarg->next) { if (cli->privilege < optarg->privilege) continue; - if ((optarg->mode!=cli->mode) && (optarg->mode != cli->transient_mode) && (optarg->mode != MODE_ANY)) continue; + if ((optarg->mode != cli->mode) && (optarg->mode != cli->transient_mode) && (optarg->mode != MODE_ANY)) continue; if (optarg->flags & CLI_CMD_DO_NOT_RECORD) continue; if (optarg->flags & CLI_CMD_ARGUMENT) { - cli_error(cli, "Incomplete command, missing required argument '%s'",optarg->name); - stage->status=CLI_MISSING_ARGUMENT; + cli_error(cli, "Incomplete command, missing required argument '%s'", optarg->name); + stage->status = CLI_MISSING_ARGUMENT; return; } } } - return ; + return; } - - diff --git a/libcli.h b/libcli.h index d5ddd48..ce4bdf6 100644 --- a/libcli.h +++ b/libcli.h @@ -92,7 +92,11 @@ struct cli_filter { struct cli_filter *next; }; -enum command_types { CLI_REGULAR_COMMAND, CLI_FILTER_COMMAND, CLI_BUILDMODE_COMMAND }; +enum command_types { + CLI_REGULAR_COMMAND, + CLI_FILTER_COMMAND, + CLI_BUILDMODE_COMMAND +}; struct cli_command { char *command; @@ -106,28 +110,27 @@ struct cli_command { struct cli_command *parent; struct cli_optarg *optargs; int (*filter)(struct cli_def *cli, const char *string, void *data); - int (*init) (struct cli_def *cli, int, char **, struct cli_filter *filt); + int (*init)(struct cli_def *cli, int, char **, struct cli_filter *filt); int command_type; }; - struct cli_comphelp { int comma_separated; char **entries; int num_entries; }; -enum optarg_flags { CLI_CMD_OPTIONAL_FLAG = 1 << 0, - CLI_CMD_OPTIONAL_ARGUMENT = 1 << 1, - CLI_CMD_ARGUMENT = 1 << 2, - CLI_CMD_ALLOW_BUILDMODE = 1 << 3, - CLI_CMD_OPTION_MULTIPLE = 1 << 4, - CLI_CMD_OPTION_SEEN = 1 << 5, - CLI_CMD_TRANSIENT_MODE = 1 << 6, - CLI_CMD_DO_NOT_RECORD = 1 << 7, - CLI_CMD_REMAINDER_OF_LINE = 1 << 8, - }; - +enum optarg_flags { + CLI_CMD_OPTIONAL_FLAG = 1 << 0, + CLI_CMD_OPTIONAL_ARGUMENT = 1 << 1, + CLI_CMD_ARGUMENT = 1 << 2, + CLI_CMD_ALLOW_BUILDMODE = 1 << 3, + CLI_CMD_OPTION_MULTIPLE = 1 << 4, + CLI_CMD_OPTION_SEEN = 1 << 5, + CLI_CMD_TRANSIENT_MODE = 1 << 6, + CLI_CMD_DO_NOT_RECORD = 1 << 7, + CLI_CMD_REMAINDER_OF_LINE = 1 << 8, +}; struct cli_optarg { char *name; @@ -164,7 +167,7 @@ struct cli_pipeline { char *cmdline; char *words[CLI_MAX_LINE_WORDS]; int num_words; - int num_stages; + int num_stages; struct cli_pipeline_stage stage[CLI_MAX_LINE_WORDS]; }; @@ -175,7 +178,7 @@ struct cli_buildmode { char *cname; int mode; int transient_mode; - char * mode_text; + char *mode_text; }; struct cli_def *cli_init(); @@ -222,20 +225,21 @@ void cli_free_comphelp(struct cli_comphelp *comphelp); int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry); void cli_set_transient_mode(struct cli_def *cli, int transient_mode); struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, - int(*init) (struct cli_def *cli, int, char **, struct cli_filter *filt), - int(*filter)(struct cli_def *, const char *, void *), - int privilege, int mode, const char *help); + int (*init)(struct cli_def *cli, int, char **, struct cli_filter *filt), + int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode, + const char *help); int cli_unregister_filter(struct cli_def *cli, const char *command); -int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int priviledge, int mode, const char *help, - int (*get_completions)(struct cli_def *cli, const char*, const char *, struct cli_comphelp * ), - int (*validator)(struct cli_def *cli, const char *, const char *), - int (*transient_mode)(struct cli_def *, const char *, const char *)); -char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) ; -struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); +int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int priviledge, int mode, + const char *help, + int (*get_completions)(struct cli_def *cli, const char *, const char *, struct cli_comphelp *), + int (*validator)(struct cli_def *cli, const char *, const char *), + int (*transient_mode)(struct cli_def *, const char *, const char *)); +char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after); +struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); int cli_unregister_optarg(struct cli_command *cmd, const char *name); -char * cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); +char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); void cli_unregister_all(struct cli_def *cli, struct cli_command *command); -void cli_unregister_all_optarg(struct cli_optarg *optarg) ; +void cli_unregister_all_optarg(struct cli_optarg *optarg); #ifdef __cplusplus } From 2fdacfc878679c525d8c6754f543585dda183cc7 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 16 Jul 2019 12:04:14 -0400 Subject: [PATCH 07/25] Rework begin/between to use same init/filter code (cli_range*), fix grep/egrep/include/exclude to use optargs, new hyphenated optarg --- libcli.c | 249 +++++++++++++++++++++++++++---------------------------- libcli.h | 2 + 2 files changed, 123 insertions(+), 128 deletions(-) diff --git a/libcli.c b/libcli.c index e51e8be..8addab5 100644 --- a/libcli.c +++ b/libcli.c @@ -130,9 +130,9 @@ struct cli_filter_cmds { } while (0) // Forward defines of *INTERNAL* library function as static here +static int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value); static int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); -static int cli_begin_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt); static int cli_match_filter(struct cli_def *cli, const char *string, void *data); static int cli_range_filter(struct cli_def *cli, const char *string, void *data); @@ -547,24 +547,44 @@ struct cli_def *cli_init() { "Configure from the terminal"); // and now the built in filters - c = cli_register_filter(cli, "begin", cli_begin_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); - cli_register_optarg(c, "begin_at", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, - "Begin with lines that match", NULL, NULL, NULL); + cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Begin showing lines that match", NULL, NULL, NULL); c = cli_register_filter(cli, "between", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Between lines that match"); + cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Begin showing lines that match", NULL, NULL, NULL); + cli_register_optarg(c, "range_end", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Stop showing lines that match", NULL, NULL, NULL); c = cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Count of lines"); + c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exclude lines that match"); + cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, + MODE_ANY, "Search pattern", NULL, NULL, NULL); + c = cli_register_filter(cli, "include", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match"); + cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, + MODE_ANY, "Search pattern", NULL, NULL, NULL); + c = cli_register_filter(cli, "grep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match regex (options: -v, -i, -e)"); + cli_register_optarg(c, "search_flags", CLI_CMD_HYPHENATED_OPTION, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Search flags (-[ivx]", NULL, cli_search_flags_validator, NULL); + cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, + MODE_ANY, "Search pattern", NULL, NULL, NULL); + c = cli_register_filter(cli, "egrep", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Include lines that match extended regex"); + cli_register_optarg(c, "search_flags", CLI_CMD_HYPHENATED_OPTION, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Search flags (-[ivx]", NULL, cli_search_flags_validator, NULL); + cli_register_optarg(c, "search_pattern", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, + MODE_ANY, "Search pattern", NULL, NULL, NULL); cli->privilege = cli->mode = -1; cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); @@ -1757,80 +1777,67 @@ struct cli_match_filter_state { } match; }; -int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { - struct cli_match_filter_state *state; - int rflags; - int i; - char *p; +int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value) { + // valid search flags starts with a hyphen, then any number of i,v, or e characters - if (argc < 2) { - if (cli->client) fprintf(cli->client, "Match filter requires an argument\r\n"); + if ((*value++ == '-') && (*value) && (strspn(value, "vie") == strlen(value))) return CLI_OK; + return CLI_ERROR; +} - return CLI_ERROR; - } +int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { + struct cli_match_filter_state *state; + char *search_pattern = cli_get_optarg_value(cli, "search_pattern", NULL); + char *search_flags = cli_get_optarg_value(cli, "search_flags", NULL); filt->filter = cli_match_filter; filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1); if (!state) return CLI_ERROR; - if (argv[0][0] == 'i' || (argv[0][0] == 'e' && argv[0][1] == 'x')) // include/exclude - { - if (argv[0][0] == 'e') state->flags = MATCH_INVERT; - - state->match.string = join_words(argc - 1, argv + 1); - return CLI_OK; - } - -#ifdef WIN32 - /* - * No regex functions in windows, so return an error - */ - return CLI_ERROR; -#endif - - state->flags = MATCH_REGEX; - - // grep/egrep - rflags = REG_NOSUB; - if (argv[0][0] == 'e') // egrep - rflags |= REG_EXTENDED; - - i = 1; - while (i < argc - 1 && argv[i][0] == '-' && argv[i][1]) { - int last = 0; - p = &argv[i][1]; - - if (strspn(p, "vie") != strlen(p)) break; - - while (*p) { - switch (*p++) { - case 'v': - state->flags |= MATCH_INVERT; - break; + if (!strcmp(cli->pipeline->current_stage->words[0], "include")) { + state->match.string = strdup(search_pattern); + } else if (!strcmp(cli->pipeline->current_stage->words[0], "exclude")) { + state->match.string = strdup(search_pattern); + state->flags = MATCH_INVERT; +#ifndef WIN32 + } else { + int rflags = REG_NOSUB; + if (!strcmp(cli->pipeline->current_stage->words[0], "grep")) { + state->flags = MATCH_REGEX; + } else if (!strcmp(cli->pipeline->current_stage->words[0], "egrep")) { + state->flags = MATCH_REGEX; + rflags |= REG_EXTENDED; + } + if (search_flags) { + char *p = search_flags++; + while (*p) { + switch (*p++) { + case 'v': + state->flags |= MATCH_INVERT; + break; - case 'i': - rflags |= REG_ICASE; - break; + case 'i': + rflags |= REG_ICASE; + break; - case 'e': - last++; - break; + case 'e': // implies next term is search string, so stop processing flags + break; + } } } - - i++; - if (last) break; + if (regcomp(&state->match.re, search_pattern, rflags)) { + if (cli->client) fprintf(cli->client, "Invalid pattern \"%s\"\r\n", search_pattern); + return CLI_ERROR; + } } - - p = join_words(argc - i, argv + i); - if ((i = regcomp(&state->match.re, p, rflags))) { - if (cli->client) fprintf(cli->client, "Invalid pattern \"%s\"\r\n", p); - - free_z(p); +#else + } else { + /* + * No regex functions in windows, so return an error + */ return CLI_ERROR; } +#endif - free_z(p); return CLI_OK; } @@ -1871,50 +1878,13 @@ struct cli_range_filter_state { char *to; }; -int cli_begin_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { - struct cli_range_filter_state *state; - char *from = strdup(cli_get_optarg_value(cli, "begin_at", NULL)); - char *to = 0; - - filt->filter = cli_range_filter; - filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1); - if (state) { - state->from = from; - state->to = to; - return CLI_OK; - } else { - free_z(from); - free_z(to); - return CLI_ERROR; - } -} - int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt) { struct cli_range_filter_state *state; - char *from = 0; - char *to = 0; - - if (!strncmp(argv[0], "bet", 3)) // between - { - if (argc < 3) { - if (cli->client) fprintf(cli->client, "Between filter requires 2 arguments\r\n"); - - return CLI_ERROR; - } - - if (!(from = strdup(argv[1]))) return CLI_ERROR; - to = join_words(argc - 2, argv + 2); - } else // begin - { - if (argc < 2) { - if (cli->client) fprintf(cli->client, "Begin filter requires an argument\r\n"); - - return CLI_ERROR; - } - - from = join_words(argc - 1, argv + 1); - } + char *from = strdup(cli_get_optarg_value(cli, "range_start", NULL)); + char *to = strdup(cli_get_optarg_value(cli, "range_end", NULL)); + // Do not have to check from/to since we would not have gotten here if we were + // missing a required argument filt->filter = cli_range_filter; filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1); if (state) { @@ -2719,8 +2689,11 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline int i; int rc = CLI_OK; - struct cli_command *first_command = NULL; + + if (!pipeline) return CLI_ERROR; + cli->pipeline = pipeline; + cli->found_optargs = NULL; if (cli->buildmode && cli->buildmode->commands) { first_command = cli->buildmode->commands; @@ -2730,6 +2703,7 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline for (i = 0; i < pipeline->num_stages; i++) { // in 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs + cli->pipeline->current_stage = &pipeline->stage[i]; if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; else @@ -2745,6 +2719,8 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline if (rc != CLI_OK) break; } + cli->pipeline = NULL; + return rc; } @@ -2837,32 +2813,38 @@ int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) int rc = CLI_OK; struct cli_filter **filt = &cli->filters; - if (pipeline) { - for (stage_num = 1; stage_num < pipeline->num_stages; stage_num++) { - struct cli_pipeline_stage *stage = &pipeline->stage[stage_num]; - cli->found_optargs = stage->found_optargs; - *filt = calloc(sizeof(struct cli_filter), 1); - if (*filt) { - if ((rc = stage->command->init(cli, stage->num_words, stage->words, *filt) != CLI_OK)) { - break; - } - filt = &(*filt)->next; + if (!pipeline) return CLI_ERROR; + + cli->pipeline = pipeline; + for (stage_num = 1; stage_num < pipeline->num_stages; stage_num++) { + struct cli_pipeline_stage *stage = &pipeline->stage[stage_num]; + pipeline->current_stage = stage; + cli->found_optargs = stage->found_optargs; + *filt = calloc(sizeof(struct cli_filter), 1); + if (*filt) { + if ((rc = stage->command->init(cli, stage->num_words, stage->words, *filt) != CLI_OK)) { + break; } + filt = &(*filt)->next; } + } + pipeline->current_stage = NULL; - // Did everything init? If so, execute, otherwise skip execution - if ((rc == CLI_OK) && pipeline->stage[0].command->callback) { - struct cli_pipeline_stage *stage = &pipeline->stage[0]; + // Did everything init? If so, execute, otherwise skip execution + if ((rc == CLI_OK) && pipeline->stage[0].command->callback) { + struct cli_pipeline_stage *stage = &pipeline->stage[0]; - if (cli->buildmode) - cli->found_optargs = cli->buildmode->found_optargs; - else - cli->found_optargs = pipeline->stage[0].found_optargs; - rc = stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words + stage->first_unmatched, - stage->num_words - stage->first_unmatched); - if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; - } + pipeline->current_stage = &pipeline->stage[0]; + if (cli->buildmode) + cli->found_optargs = cli->buildmode->found_optargs; + else + cli->found_optargs = pipeline->stage[0].found_optargs; + rc = stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words + stage->first_unmatched, + stage->num_words - stage->first_unmatched); + if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; + pipeline->current_stage = NULL; } + // Now teardown any filters while (cli->filters) { struct cli_filter *filt = cli->filters; @@ -2871,6 +2853,7 @@ int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) free_z(filt); } cli->found_optargs = NULL; + cli->pipeline = NULL; return rc; } @@ -2903,6 +2886,9 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta delim_end = DELIM_OPT_END; get_completions = NULL; // no point, completor of field is the name itself } + } else if (optarg->flags & CLI_CMD_HYPHENATED_OPTION) { + delim_start = DELIM_OPT_START; + delim_end = DELIM_OPT_END; } else if (optarg->flags & CLI_CMD_ARGUMENT) { delim_start = DELIM_ARG_START; delim_end = DELIM_ARG_END; @@ -2994,7 +2980,9 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (cli->privilege < oaptr->privilege) continue; if ((oaptr->mode != cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue; - /* An exact match for an optional flag/argument name trumps anything and will be the *only* candidate + /* Two special cases - a hphenated option and an 'exact' match optional flag or optional argument + * If our word starts with a '-' and we have a CMD_CLI_HYPHENATED_OPTION or an exact match for an + * optional flag/argument name trumps anything and will be the *only* candidate * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a candidate * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are required */ @@ -3003,6 +2991,10 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage candidates[0] = oaptr; num_candidates = 1; break; + } else if (stage->words[w_idx] && stage->words[w_idx][0] == '-' && (oaptr->flags & (CLI_CMD_HYPHENATED_OPTION))) { + candidates[0] = oaptr; + num_candidates = 1; + break; } else if (!stage->words[w_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || !strncasecmp(oaptr->name, stage->words[w_idx], strlen(stage->words[w_idx]))) { candidates[num_candidates++] = oaptr; @@ -3038,7 +3030,8 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage // need to know *which* word we're trying to complete for optional_args, hence the difference calls if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (w_idx == (stage->num_words - 1))) || - ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 1)))) { + ((oaptr->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_HYPHENATED_OPTION)) && + (w_idx == (stage->num_words - 1)))) { cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], stage->words[w_idx]); called_comphelp = 1; diff --git a/libcli.h b/libcli.h index ce4bdf6..0f03710 100644 --- a/libcli.h +++ b/libcli.h @@ -130,6 +130,7 @@ enum optarg_flags { CLI_CMD_TRANSIENT_MODE = 1 << 6, CLI_CMD_DO_NOT_RECORD = 1 << 7, CLI_CMD_REMAINDER_OF_LINE = 1 << 8, + CLI_CMD_HYPHENATED_OPTION = 1 << 9, }; struct cli_optarg { @@ -169,6 +170,7 @@ struct cli_pipeline { int num_words; int num_stages; struct cli_pipeline_stage stage[CLI_MAX_LINE_WORDS]; + struct cli_pipeline_stage *current_stage; }; struct cli_buildmode { From db64c38142a44eeb1cc18ea989080adcb4dfc097 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 16 Jul 2019 17:15:56 -0400 Subject: [PATCH 08/25] Rework so *all* commands go in cli->commands, struct cli_command has field indicating type - have cli_register_command, cli_register_filter, cli_int_register_buildmode_command to call cli_int_register_command passing constructed cli_command ptr with correct buidltype - similar for cli_unregister_* - changed cli_unrgister_all to be cli_unregister_tree now passing in type, fix for issue where command to be deleted is 'first' command (cli->command == command to delete) --- libcli.c | 322 ++++++++++++++++++++++++++----------------------------- libcli.h | 17 ++- 2 files changed, 159 insertions(+), 180 deletions(-) diff --git a/libcli.c b/libcli.c index 8addab5..10d104b 100644 --- a/libcli.c +++ b/libcli.c @@ -37,9 +37,13 @@ * Stupid windows has multiple namespaces for filedescriptors, with different * read/write functions required for each .. */ -int read(int fd, void *buf, unsigned int count) { return recv(fd, buf, count, 0); } +int read(int fd, void *buf, unsigned int count) { + return recv(fd, buf, count, 0); +} -int write(int fd, const void *buf, unsigned int count) { return send(fd, buf, count, 0); } +int write(int fd, const void *buf, unsigned int count) { + return send(fd, buf, count, 0); +} int vasprintf(char **strp, const char *fmt, va_list args) { int size; @@ -144,6 +148,7 @@ static char *cli_int_buildmode_extend_cmdline(char *, char *word); static void cli_int_free_buildmode(struct cli_def *cli); static int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); static void cli_free_command(struct cli_command *cmd); +static int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type); static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)); static struct cli_command *cli_int_register_buildmode_command( struct cli_def *cli, struct cli_command *parent, const char *command, @@ -166,6 +171,8 @@ static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *p static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline); +static struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, + struct cli_command *c); static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; @@ -297,7 +304,7 @@ static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands) c->unique_len = 1; for (p = commands; p; p = p->next) { if (c == p) continue; - + if (c->command_type != p->command_type) continue; if ((p->mode != MODE_ANY && p->mode != cli->mode) || p->privilege > cli->privilege) continue; cp = c->command; @@ -322,7 +329,6 @@ int cli_set_privilege(struct cli_def *cli, int priv) { if (priv != old) { cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? "# " : "> "); cli_build_shortest(cli, cli->commands); - if (cli->filter_commands) cli_build_shortest(cli, cli->filter_commands); } return old; @@ -350,20 +356,44 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) { } cli_build_shortest(cli, cli->commands); - cli_build_shortest(cli, cli->filter_commands); } return old; } +struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c) { + struct cli_command *p; + + if (!c) return NULL; + + if (parent) { + if (!parent->children) { + parent->children = c; + } else { + for (p = parent->children; p && p->next; p = p->next) + ; + if (p) p->next = c; + } + } else { + if (!cli->commands) { + cli->commands = c; + } else { + for (p = cli->commands; p && p->next; p = p->next) + ; + if (p) p->next = c; + } + } + return c; +} + struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command, int (*callback)(struct cli_def *cli, const char *, char **, int), int privilege, int mode, const char *help) { - struct cli_command *c, *p; + struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; - + c->command_type = CLI_REGULAR_COMMAND; c->callback = callback; c->next = NULL; if (!(c->command = strdup(command))) { @@ -380,22 +410,9 @@ struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command return NULL; } - if (parent) { - if (!parent->children) { - parent->children = c; - } else { - for (p = parent->children; p && p->next; p = p->next) - ; - if (p) p->next = c; - } - } else { - if (!cli->commands) { - cli->commands = c; - } else { - for (p = cli->commands; p && p->next; p = p->next) - ; - if (p) p->next = c; - } + if (!cli_register_command_core(cli, parent, c)) { + cli_free_command(c); + c = NULL; } return c; } @@ -411,18 +428,18 @@ static void cli_free_command(struct cli_command *cmd) { free(cmd->command); if (cmd->help) free(cmd->help); - if (cmd->optargs) cli_unregister_all_optarg(cmd->optargs); + if (cmd->optargs) cli_unregister_all_optarg(cmd); free(cmd); } -int cli_unregister_command(struct cli_def *cli, const char *command) { +int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type) { struct cli_command *c, *p = NULL; if (!command) return -1; if (!cli->commands) return CLI_OK; for (c = cli->commands; c; c = c->next) { - if (strcmp(c->command, command) == 0) { + if ((strcmp(c->command, command) == 0) && (c->command_type == command_type)) { if (p) p->next = c->next; else @@ -437,6 +454,10 @@ int cli_unregister_command(struct cli_def *cli, const char *command) { return CLI_OK; } +int cli_unregister_command(struct cli_def *cli, const char *command) { + return cli_int_unregister_command_core(cli, command, CLI_REGULAR_COMMAND); +} + int cli_show_help(struct cli_def *cli, struct cli_command *c) { struct cli_command *p; @@ -599,22 +620,23 @@ struct cli_def *cli_init() { return cli; } -void cli_unregister_all(struct cli_def *cli, struct cli_command *command) { +void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int command_type) { struct cli_command *c, *p = NULL; if (!command) command = cli->commands; - if (!command) return; for (c = command; c;) { p = c->next; - - // Unregister all child commands - if (c->children) cli_unregister_all(cli, c->children); - if (c->optargs) cli_unregister_all_optarg(c->optargs); - if (c->command) free(c->command); - if (c->help) free(c->help); - free(c); - + if ((c->command_type == command_type) || (command_type == CLI_ANY_COMMAND)) { + // handle case where we're deleting *first* command + if (c == cli->commands) cli->commands = p; + // Unregister all child commands + if (c->children) cli_unregister_tree(cli, c->children, command_type); + if (c->optargs) cli_unregister_all_optarg(c); + if (c->command) free(c->command); + if (c->help) free(c->help); + free(c); + } c = p; } } @@ -634,11 +656,8 @@ int cli_done(struct cli_def *cli) { u = n; } - /* free all commands and filters */ - cli_unregister_all(cli, cli->commands); - cli_unregister_all(cli, cli->filter_commands); - if (cli->buildmode) cli_int_free_buildmode(cli); + cli_unregister_tree(cli, cli->commands, CLI_ANY_COMMAND); free_z(cli->commandname); free_z(cli->modestring); free_z(cli->banner); @@ -751,7 +770,7 @@ int cli_run_command(struct cli_def *cli, const char *command) { pipeline = cli_int_generate_pipeline(cli, command); - // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_COMMAND_START if found. + // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_START if found. if (pipeline) rc = cli_int_validate_pipeline(cli, pipeline); if (rc == CLI_OK) { @@ -764,9 +783,9 @@ int cli_run_command(struct cli_def *cli, const char *command) { void cli_get_completions(struct cli_def *cli, const char *command, char lastchar, struct cli_comphelp *comphelp) { struct cli_command *c = NULL; struct cli_command *n = NULL; - struct cli_command *first_command = NULL; int i; + int command_type; struct cli_pipeline *pipeline = NULL; struct cli_pipeline_stage *stage; @@ -778,18 +797,18 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words - 1])) stage->num_words++; - if (cli->buildmode && cli->buildmode->commands) { - first_command = cli->buildmode->commands; - } else if (pipeline->num_stages == 1) { - first_command = cli->commands; - } else { - first_command = cli->filter_commands; - } + if (cli->buildmode) + command_type = CLI_BUILDMODE_COMMAND; + else if (pipeline->num_stages == 1) + command_type = CLI_REGULAR_COMMAND; + else + command_type = CLI_FILTER_COMMAND; - for (c = first_command, i = 0; c && i < stage->num_words; c = n) { + for (c = cli->commands, i = 0; c && i < stage->num_words; c = n) { char *strptr = NULL; n = c->next; + if (c->command_type != command_type) continue; if (cli->privilege < c->privilege) continue; if (c->mode != cli->mode && c->mode != MODE_ANY) continue; @@ -799,7 +818,7 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar // special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set if (cli->buildmode) { struct cli_optarg *optarg; - for (optarg = cli->buildmode->c->optargs; optarg; optarg = optarg->next) { + for (optarg = cli->buildmode->command->optargs; optarg; optarg = optarg->next) { if (!strcmp(optarg->name, c->command)) break; } if (optarg && cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) @@ -924,7 +943,6 @@ int cli_loop(struct cli_def *cli, int sockfd) { char *username = NULL, *password = NULL; cli_build_shortest(cli, cli->commands); - cli_build_shortest(cli, cli->filter_commands); cli->state = STATE_LOGIN; cli_free_history(cli); @@ -1624,25 +1642,25 @@ int cli_loop(struct cli_def *cli, int sockfd) { rc = cli_run_command(cli, cmd); switch (rc) { - case CLI_BUILDMODE_COMMAND_ERROR: + case CLI_BUILDMODE_ERROR: // unable to enter buildmode successfully cli_print(cli, "Failure entering build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); continue; - case CLI_BUILDMODE_COMMAND_CANCEL: + case CLI_BUILDMODE_CANCEL: // called if user enters 'cancel' cli_print(cli, "Canceling build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); break; - case CLI_BUILDMODE_COMMAND_EXIT: + case CLI_BUILDMODE_EXIT: // called when user enters exit - rebuild *entire* command line // recall all located optargs cli->found_optargs = cli->buildmode->found_optargs; rc = cli_int_execute_buildmode(cli); case CLI_QUIT: break; - case CLI_BUILDMODE_COMMAND_START: - case CLI_BUILDMODE_COMMAND_EXTEND: + case CLI_BUILDMODE_START: + case CLI_BUILDMODE_EXTEND: default: break; } @@ -1751,7 +1769,9 @@ void cli_bufprint(struct cli_def *cli, const char *format, ...) { va_end(ap); } -void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) { _print(cli, PRINT_BUFFERED, format, ap); } +void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) { + _print(cli, PRINT_BUFFERED, format, ap); +} void cli_print(struct cli_def *cli, const char *format, ...) { va_list ap; @@ -1967,24 +1987,27 @@ void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, in cli->idle_timeout_callback = callback; } -void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) { cli->telnet_protocol = !!telnet_protocol; } - -void cli_set_context(struct cli_def *cli, void *context) { cli->user_context = context; } +void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) { + cli->telnet_protocol = !!telnet_protocol; +} +void cli_set_context(struct cli_def *cli, void *context) { + cli->user_context = context; +} -void *cli_get_context(struct cli_def *cli) { return cli->user_context; } +void *cli_get_context(struct cli_def *cli) { + return cli->user_context; +} -/* cli_register_filter/cli_unregister_filter are limited coies of cli_register_command/cli_unregister_command. - * Filters do not have a hierarchy, so they are all siblings of each other so we only need the 'next' field. - */ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command, int (*init)(struct cli_def *cli, int, char **, struct cli_filter *), int (*filter)(struct cli_def *, const char *, void *), int privilege, int mode, const char *help) { - struct cli_command *c, *p; + struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; + c->command_type = CLI_FILTER_COMMAND; c->init = init; c->filter = filter; c->next = NULL; @@ -2000,35 +2023,16 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command free(c); return NULL; } - - if (!cli->filter_commands) { - cli->filter_commands = c; - } else { - for (p = cli->filter_commands; p && p->next; p = p->next) - ; - if (p) p->next = c; + // filters are all registered at the top level + if (!cli_register_command_core(cli, NULL, c)) { + cli_free_command(c); + c = NULL; } return c; } int cli_unregister_filter(struct cli_def *cli, const char *command) { - struct cli_command *c, *p = NULL; - - if (!command) return -1; - if (!cli->filter_commands) return CLI_OK; - - for (c = cli->filter_commands; c; c = c->next) { - if (strcmp(c->command, command) == 0) { - if (p) - p->next = c->next; - else - cli->filter_commands = c->next; - cli_free_command(c); - return CLI_OK; - } - p = c; - } - return CLI_OK; + return cli_int_unregister_command_core(cli, command, CLI_FILTER_COMMAND); } void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair) { @@ -2153,12 +2157,12 @@ int cli_unregister_optarg(struct cli_command *cmd, const char *name) { return retval; } -void cli_unregister_all_optarg(struct cli_optarg *optarg) { - struct cli_optarg *p; +void cli_unregister_all_optarg(struct cli_command *c) { + struct cli_optarg *o, *p; - for (; optarg; optarg = p) { - p = optarg->next; - cli_free_optarg(optarg); + for (o = c->optargs; o; o = p) { + p = o->next; + cli_free_optarg(o); } } @@ -2237,8 +2241,9 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft } void cli_int_free_buildmode(struct cli_def *cli) { + if (!cli || !cli->buildmode) return; - if (cli->buildmode->commands) cli_unregister_all(cli, cli->buildmode->commands); + // cli_unregister_tree(cli, cli->commands, CLI_BUILDMODE_COMMAND); cli->mode = cli->buildmode->mode; free_z(cli->buildmode->cname); free_z(cli->buildmode->mode_text); @@ -2253,7 +2258,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag if ((!cli) || !(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) { cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command); - return CLI_BUILDMODE_COMMAND_ERROR; + return CLI_BUILDMODE_ERROR; } // clean up any shrapnel from earlier - shouldn't be any but.... @@ -2268,14 +2273,14 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag if (mode_text) cli->buildmode->mode_text = strdup(mode_text); // need this to verify we have all *required* arguments - cli->buildmode->c = stage->command; + cli->buildmode->command = stage->command; // build new *limited* list of commands from this commands optargs for (optarg = stage->command->optargs; optarg; optarg = optarg->next) { // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel if (!strcmp(optarg->name, "cancel") || (!strcmp(optarg->name, "exit"))) { cli_error(cli, "Unable to build buildmode mode from optarg named %s", optarg->name); - return CLI_BUILDMODE_COMMAND_ERROR; + return CLI_BUILDMODE_ERROR; } if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE)) continue; if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) @@ -2287,18 +2292,18 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, NULL); } else { - return CLI_BUILDMODE_COMMAND_ERROR; + return CLI_BUILDMODE_ERROR; } } else { if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, optarg->privilege, cli->mode, optarg->help)) { - return CLI_BUILDMODE_COMMAND_ERROR; + return CLI_BUILDMODE_ERROR; } } else { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->privilege, cli->mode, optarg->help)) { - return CLI_BUILDMODE_COMMAND_ERROR; + return CLI_BUILDMODE_ERROR; } } } @@ -2316,40 +2321,18 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT | CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode, "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); - cli_build_shortest(cli, cli->buildmode->commands); - - return CLI_BUILDMODE_COMMAND_START; + return CLI_BUILDMODE_START; } int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) { - struct cli_command *c, *p = NULL; - - if (!command) return -1; - if (!cli->buildmode || !cli->buildmode->commands) return CLI_OK; - - for (c = cli->buildmode->commands; c; c = c->next) { - if (strcmp(c->command, command) == 0) { - if (p) - p->next = c->next; - else - cli->buildmode->commands = c->next; - - cli_free_command(c); - return CLI_OK; - } - p = c; - } - - return CLI_OK; + return cli_int_unregister_command_core(cli, command, CLI_BUILDMODE_COMMAND); } -// a copy of cli_register_command, but using cli->buildmode_cmd rather than cli->commands as the anchor - struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, struct cli_command *parent, const char *command, int (*callback)(struct cli_def *cli, const char *, char **, int), int privilege, int mode, const char *help) { - struct cli_command *c, *p; + struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; @@ -2361,6 +2344,7 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru return NULL; } + c->command_type = CLI_BUILDMODE_COMMAND; c->parent = parent; c->privilege = privilege; c->mode = mode; @@ -2369,23 +2353,10 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru free(c); return NULL; } - - if (parent) { - if (!parent->children) { - parent->children = c; - } else { - for (p = parent->children; p && p->next; p = p->next) - ; - if (p) p->next = c; - } - } else { - if (!cli->buildmode->commands) { - cli->buildmode->commands = c; - } else { - for (p = cli->buildmode->commands; p && p->next; p = p->next) - ; - if (p) p->next = c; - } + // buildmode commmands are all registered at the top level + if (!cli_register_command_core(cli, NULL, c)) { + cli_free_command(c); + c = NULL; } return c; } @@ -2397,8 +2368,8 @@ int cli_int_execute_buildmode(struct cli_def *cli) { char *value = NULL; - cmdline = strdup(cli_command_name(cli, cli->buildmode->c)); - for (optarg = cli->buildmode->c->optargs; rc == CLI_OK && optarg; optarg = optarg->next) { + cmdline = strdup(cli_command_name(cli, cli->buildmode->command)); + for (optarg = cli->buildmode->command->optargs; rc == CLI_OK && optarg; optarg = optarg->next) { value = NULL; do { if (cli->privilege < optarg->privilege) continue; @@ -2467,7 +2438,7 @@ char *cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { } int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc = CLI_BUILDMODE_COMMAND_EXTEND; + int rc = CLI_BUILDMODE_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); @@ -2478,7 +2449,7 @@ int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char * // a 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc = CLI_BUILDMODE_COMMAND_EXTEND; + int rc = CLI_BUILDMODE_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); @@ -2493,7 +2464,7 @@ int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char // a 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc = CLI_BUILDMODE_COMMAND_EXTEND; + int rc = CLI_BUILDMODE_EXTEND; if (argc) { cli_error(cli, "Extra arguments on command line, command ignored."); @@ -2508,7 +2479,7 @@ int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *comma } int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc = CLI_BUILDMODE_COMMAND_CANCEL; + int rc = CLI_BUILDMODE_CANCEL; if (argc > 0) { cli_error(cli, "Extra arguments on command line, command ignored."); @@ -2518,7 +2489,7 @@ int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, cha } int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - int rc = CLI_BUILDMODE_COMMAND_EXIT; + int rc = CLI_BUILDMODE_EXIT; if (argc > 0) { cli_error(cli, "Extra arguments on command line, command ignored."); @@ -2532,8 +2503,9 @@ int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char if (cli && cli->buildmode) { for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { // only show vars that are also current 'commands' - struct cli_command *c = cli->buildmode->commands; + struct cli_command *c = cli->commands; for (; c; c = c->next) { + if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (!strcmp(c->command, optarg_pair->name)) { cli_print(cli, " %-20s = %s", optarg_pair->name, optarg_pair->value); break; @@ -2550,7 +2522,8 @@ int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char struct cli_command *c; // is this 'optarg' to remove one of the current commands? - for (c = cli->buildmode->commands; c; c = c->next) { + for (c = cli->commands; c; c = c->next) { + if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (cli->privilege < c->privilege) continue; if ((cli->buildmode->mode != c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY)) continue; @@ -2570,9 +2543,13 @@ int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, con return CLI_OK; } -int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { return CLI_OK; } +int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { + return CLI_OK +} -void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { cli->transient_mode = transient_mode; } +void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { + cli->transient_mode = transient_mode; +} int cli_add_comphelp_entry(struct cli_comphelp *comphelp, const char *entry) { int retval = CLI_ERROR; @@ -2600,12 +2577,13 @@ void cli_free_comphelp(struct cli_comphelp *comphelp) { } } -static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, int start_word, +static int cli_int_locate_command(struct cli_def *cli, struct cli_command *commands, int command_type, int start_word, struct cli_pipeline_stage *stage) { struct cli_command *c, *again_config = NULL, *again_any = NULL; int c_words = stage->num_words; for (c = commands; c; c = c->next) { + if (c->command_type != command_type) continue; if (cli->privilege < c->privilege) continue; if (strncasecmp(c->command, stage->words[start_word], c->unique_len)) continue; @@ -2630,7 +2608,7 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma cli_error(cli, "Incomplete command"); return CLI_ERROR; } - rc = cli_int_locate_command(cli, c->children, start_word + 1, stage); + rc = cli_int_locate_command(cli, c->children, command_type, start_word + 1, stage); if (rc == CLI_ERROR_ARG) { if (c->callback) { rc = CLI_OK; @@ -2689,26 +2667,28 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline int i; int rc = CLI_OK; - struct cli_command *first_command = NULL; + int command_type; if (!pipeline) return CLI_ERROR; cli->pipeline = pipeline; cli->found_optargs = NULL; - if (cli->buildmode && cli->buildmode->commands) { - first_command = cli->buildmode->commands; - } else { - first_command = cli->commands; - } for (i = 0; i < pipeline->num_stages; i++) { // in 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs + if (cli->buildmode && i == 0) + command_type = CLI_BUILDMODE_COMMAND; + else if (i > 0) + command_type = CLI_FILTER_COMMAND; + else + command_type = CLI_REGULAR_COMMAND; + cli->pipeline->current_stage = &pipeline->stage[i]; if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; else cli->found_optargs = NULL; - rc = cli_int_locate_command(cli, (i == 0) ? first_command : cli->filter_commands, 0, &pipeline->stage[i]); + rc = cli_int_locate_command(cli, cli->commands, command_type, 0, &pipeline->stage[i]); // and save our found optargs for later use @@ -2813,7 +2793,7 @@ int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) int rc = CLI_OK; struct cli_filter **filt = &cli->filters; - if (!pipeline) return CLI_ERROR; + if (!pipeline | !cli) return CLI_ERROR; cli->pipeline = pipeline; for (stage_num = 1; stage_num < pipeline->num_stages; stage_num++) { @@ -2835,13 +2815,14 @@ int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) struct cli_pipeline_stage *stage = &pipeline->stage[0]; pipeline->current_stage = &pipeline->stage[0]; - if (cli->buildmode) + if (pipeline->current_stage->command->command_type == CLI_BUILDMODE_COMMAND) cli->found_optargs = cli->buildmode->found_optargs; else cli->found_optargs = pipeline->stage[0].found_optargs; rc = stage->command->callback(cli, cli_command_name(cli, stage->command), stage->words + stage->first_unmatched, stage->num_words - stage->first_unmatched); - if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; + if (pipeline->current_stage->command->command_type == CLI_BUILDMODE_COMMAND) + cli->buildmode->found_optargs = cli->found_optargs; pipeline->current_stage = NULL; } @@ -3059,14 +3040,14 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT)) { w_incr = 2; - value = stage->words[w_idx + 1]; - if (!value && lastchar == '\0') { + if (!stage->words[w_idx + 1] && lastchar == '\0') { // hit a optional argument that does not have a value with it cli_error(cli, "Optional argument %s requires a value", stage->words[w_idx]); stage->error_word = stage->words[w_idx]; stage->status = CLI_MISSING_VALUE; return; } + value = stage->words[w_idx + 1]; } /* @@ -3126,8 +3107,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage /* * Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg - * starting point - * we see a true argument + * starting point when we see a true argument */ if (oaptr->flags & CLI_CMD_ARGUMENT) { // advance pass this argument entry diff --git a/libcli.h b/libcli.h index 0f03710..e6fa3d8 100644 --- a/libcli.h +++ b/libcli.h @@ -24,11 +24,11 @@ extern "C" { #define CLI_UNRECOGNIZED -5 #define CLI_MISSING_ARGUMENT -6 #define CLI_MISSING_VALUE -7 -#define CLI_BUILDMODE_COMMAND_START -8 -#define CLI_BUILDMODE_COMMAND_ERROR -9 -#define CLI_BUILDMODE_COMMAND_EXTEND -10 -#define CLI_BUILDMODE_COMMAND_CANCEL -11 -#define CLI_BUILDMODE_COMMAND_EXIT -12 +#define CLI_BUILDMODE_START -8 +#define CLI_BUILDMODE_ERROR -9 +#define CLI_BUILDMODE_EXTEND -10 +#define CLI_BUILDMODE_CANCEL -11 +#define CLI_BUILDMODE_EXIT -12 #define MAX_HISTORY 256 @@ -79,7 +79,6 @@ struct cli_def { time_t last_action; int telnet_protocol; void *user_context; - struct cli_command *filter_commands; struct cli_optarg_pair *found_optargs; int transient_mode; struct cli_pipeline *pipeline; @@ -93,6 +92,7 @@ struct cli_filter { }; enum command_types { + CLI_ANY_COMMAND, CLI_REGULAR_COMMAND, CLI_FILTER_COMMAND, CLI_BUILDMODE_COMMAND @@ -174,8 +174,7 @@ struct cli_pipeline { }; struct cli_buildmode { - struct cli_command *c; - struct cli_command *commands; + struct cli_command *command; struct cli_optarg_pair *found_optargs; char *cname; int mode; @@ -241,7 +240,7 @@ struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); int cli_unregister_optarg(struct cli_command *cmd, const char *name); char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); void cli_unregister_all(struct cli_def *cli, struct cli_command *command); -void cli_unregister_all_optarg(struct cli_optarg *optarg); +void cli_unregister_all_optarg(struct cli_command *c); #ifdef __cplusplus } From 3ece534ea0463e471320947864900210d160dfc7 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 16 Jul 2019 17:37:10 -0400 Subject: [PATCH 09/25] Fix typo generated while manually fixing formatting on functions w/single line --- libcli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcli.c b/libcli.c index 10d104b..9da160d 100644 --- a/libcli.c +++ b/libcli.c @@ -565,7 +565,7 @@ struct cli_def *cli_init() { c = cli_register_command(cli, 0, "configure", 0, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enter configuration mode"); cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC, - "Configure from the terminal"); + "Conlfigure from the terminal"); // and now the built in filters c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, @@ -2544,7 +2544,7 @@ int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, con } int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { - return CLI_OK + return CLI_OK; } void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { From b84170fee740e4d346ad7f6631d459da967ec414 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 17 Jul 2019 08:03:17 -0400 Subject: [PATCH 10/25] Fixing multiple code review items --- clitest.c | 38 +++++++++++++++++-------------- libcli.c | 67 ++++++++++++++++++++++++++++++------------------------- libcli.h | 2 +- 3 files changed, 59 insertions(+), 48 deletions(-) diff --git a/clitest.c b/clitest.c index e38366c..36102dd 100644 --- a/clitest.c +++ b/clitest.c @@ -1,7 +1,7 @@ -#include -#include #include #include +#include +#include #ifdef WIN32 #include #include @@ -163,14 +163,18 @@ int regular_callback(struct cli_def *cli) { return CLI_OK; } -int check_enable(const char *password) { return !strcasecmp(password, "topsecret"); } +int check_enable(const char *password) { + return !strcasecmp(password, "topsecret"); +} int idle_timeout(struct cli_def *cli) { cli_print(cli, "Custom idle timeout"); return CLI_QUIT; } -void pc(UNUSED(struct cli_def *cli), const char *string) { printf("%s\n", string); } +void pc(UNUSED(struct cli_def *cli), const char *string) { + printf("%s\n", string); +} #define MODE_POLYGON_TRIANGLE 20 #define MODE_POLYGON_RECTANGLE 21 @@ -233,34 +237,30 @@ int shape_completor(struct cli_def *cli, const char *name, const char *value, st int shape_validator(struct cli_def *cli, const char *name, const char *value) { const char **shape; - int rc = CLI_ERROR; + printf("shape_validator called with <%s>\n", value); for (shape = KnownShapes; *shape; shape++) { if (!strcmp(value, *shape)) return CLI_OK; } - return rc; + return CLI_ERROR; } int verbose_validator(struct cli_def *cli, const char *name, const char *value) { - int rc = CLI_OK; printf("verbose_validator called\n"); - return rc; + return CLI_OK; } int shape_transient_eval(struct cli_def *cli, const char *name, const char *value) { - int rc = CLI_OK; printf("shape_transient_eval called with <%s>\n", value); if (!strcmp(value, "rectangle")) { cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE); - rc = CLI_OK; + return CLI_OK; } else if (!strcmp(value, "triangle")) { cli_set_transient_mode(cli, MODE_POLYGON_TRIANGLE); - rc = CLI_OK; - } else { - cli_error(cli, "unrecognized value for setting %s -> %s", name, value); - rc = CLI_ERROR; + return CLI_OK; } - return rc; + cli_error(cli, "unrecognized value for setting %s -> %s", name, value); + return CLI_ERROR; } const char *KnownColors[] = {"black", "white", "gray", "red", "blue", @@ -319,8 +319,12 @@ void run_child(int x) { cli_set_hostname(cli, "router"); cli_telnet_protocol(cli, 1); cli_regular(cli, regular_callback); - cli_regular_interval(cli, 5); // Defaults to 1 second - // cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second idle timeout + + // change regular update to 5 seconds rather than default of 1 second + cli_regular_interval(cli, 5); + + // set 60 second idle timeout + cli_set_idle_timeout_callback(cli, 60, idle_timeout); cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); diff --git a/libcli.c b/libcli.c index 9da160d..60279d8 100644 --- a/libcli.c +++ b/libcli.c @@ -110,7 +110,7 @@ enum cli_states { STATE_PASSWORD, STATE_NORMAL, STATE_ENABLE_PASSWORD, - STATE_ENABLE + STATE_ENABLE, }; struct unp { @@ -165,7 +165,7 @@ static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *na static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value); static int cli_int_execute_buildmode(struct cli_def *cli); static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair); -// static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name ) ; +static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name ) ; static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command); static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); @@ -862,7 +862,7 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } } - pipeline = cli_int_free_pipeline(pipeline); + cli_int_free_pipeline(pipeline); } static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) { @@ -905,7 +905,7 @@ void cli_regular_interval(struct cli_def *cli, int seconds) { #define DES_PREFIX "{crypt}" /* to distinguish clear text from DES crypted */ #define MD5_PREFIX "$1$" -static int pass_matches(const char *pass, const char *try) { +static int pass_matches(const char *pass, const char *attempt) { int des; if ((des = !strncasecmp(pass, DES_PREFIX, sizeof(DES_PREFIX) - 1))) pass += sizeof(DES_PREFIX) - 1; @@ -913,10 +913,10 @@ static int pass_matches(const char *pass, const char *try) { /* * TODO - find a small crypt(3) function for use on windows */ - if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) try = crypt(try, pass); + if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) attempt = crypt(attempt, pass); #endif - return !strcmp(pass, try); + return !strcmp(pass, attempt); } #define CTRL(c) (c - '@') @@ -1388,7 +1388,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); } - strncpy((cmd + l), tptr, k); + strncpy(cmd + l, tptr, k); l += k; cursor = l; _write(sockfd, tptr, k); @@ -2134,7 +2134,7 @@ int cli_unregister_optarg(struct cli_command *cmd, const char *name) { struct cli_optarg *ptr; struct cli_optarg *lastptr; int retval = CLI_ERROR; - // iterate looking for this option name, stoping at end or if name matches + // iterate looking for this option name, stopping at end or if name matches for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next) { ; } @@ -2221,9 +2221,7 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft char *value = NULL; struct cli_optarg_pair *optarg_pair; - printf("cli_get_optarg_value entry - looking for <%s> after <%p>\n", name, (void *)find_after); for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair = optarg_pair->next) { - printf(" Checking %s with value %s <%p> \n", optarg_pair->name, optarg_pair->value, (void *)optarg_pair->value); // check next entry if this isn't our name if (strcasecmp(optarg_pair->name, name)) continue; @@ -2236,12 +2234,10 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft value = optarg_pair->value; } } - printf("cli_get_optarg_value exit - returning <%s><%p>\n", name, value); return value; } void cli_int_free_buildmode(struct cli_def *cli) { - if (!cli || !cli->buildmode) return; // cli_unregister_tree(cli, cli->commands, CLI_BUILDMODE_COMMAND); cli->mode = cli->buildmode->mode; @@ -2255,10 +2251,12 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag struct cli_optarg *optarg; struct cli_command *c; struct cli_buildmode *buildmode; - - if ((!cli) || !(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) { + int rc = CLI_BUILDMODE_START; + + if (!cli || !(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) { cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command); - return CLI_BUILDMODE_ERROR; + rc = CLI_BUILDMODE_ERROR; + goto out; } // clean up any shrapnel from earlier - shouldn't be any but.... @@ -2280,7 +2278,8 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel if (!strcmp(optarg->name, "cancel") || (!strcmp(optarg->name, "exit"))) { cli_error(cli, "Unable to build buildmode mode from optarg named %s", optarg->name); - return CLI_BUILDMODE_ERROR; + rc = CLI_BUILDMODE_ERROR; + goto out; } if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE)) continue; if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) @@ -2292,18 +2291,21 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, NULL); } else { - return CLI_BUILDMODE_ERROR; + rc = CLI_BUILDMODE_ERROR; + goto out; } } else { if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, optarg->privilege, cli->mode, optarg->help)) { - return CLI_BUILDMODE_ERROR; + rc = CLI_BUILDMODE_ERROR; + goto out; } } else { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->privilege, cli->mode, optarg->help)) { - return CLI_BUILDMODE_ERROR; + rc = CLI_BUILDMODE_ERROR; + goto out; } } } @@ -2321,7 +2323,11 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag cli_register_optarg(c, "setting", CLI_CMD_ARGUMENT | CLI_CMD_DO_NOT_RECORD, PRIVILEGE_UNPRIVILEGED, cli->mode, "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); - return CLI_BUILDMODE_START; +out: + if (rc!=CLI_BUILDMODE_START) { + cli_int_free_buildmode(cli); + } + return rc; } int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) { @@ -2723,22 +2729,22 @@ void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { struct cli_optarg_pair *optarg_pair; for (i = 0, word = pipeline->words; i < pipeline->num_words; i++, word++) printf("[%s] ", *word); - printf("\n"); - printf("#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words); + fprintf(stderr, "\n"); + fprintf(stderr, "#stages=%d, #words=%d\n", pipeline->num_stages, pipeline->num_words); for (i = 0; i < pipeline->num_stages; i++) { stage = &(pipeline->stage[i]); - printf(" #%d(%d words) first_unmatched=%d: ", i, stage->num_words, stage->first_unmatched); + fprintf(stderr, " #%d(%d words) first_unmatched=%d: ", i, stage->num_words, stage->first_unmatched); for (j = 0; j < stage->num_words; j++) { - printf(" [%s]", stage->words[j]); + fprintf(stderr, " [%s]", stage->words[j]); } - printf("\n"); + fprintf(stderr, "\n"); if (stage->command) { - printf(" Command: %s\n", stage->command->command); + fprintf(stderr, " Command: %s\n", stage->command->command); } for (optarg_pair = stage->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { - printf(" %s: %s\n", optarg_pair->name, optarg_pair->value); + fprintf(stderr, " %s: %s\n", optarg_pair->name, optarg_pair->value); } } } @@ -3029,14 +3035,15 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage } } + // set some values for use later - makes code much easier to read + value = stage->words[w_idx]; + oaptr = candidates[0]; + validator = oaptr->validator; if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (w_idx == (stage->num_words - 1))) || ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 2)))) { is_last_word = 1; } - value = stage->words[w_idx]; - oaptr = candidates[0]; - validator = oaptr->validator; if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT)) { w_incr = 2; diff --git a/libcli.h b/libcli.h index e6fa3d8..9966fba 100644 --- a/libcli.h +++ b/libcli.h @@ -95,7 +95,7 @@ enum command_types { CLI_ANY_COMMAND, CLI_REGULAR_COMMAND, CLI_FILTER_COMMAND, - CLI_BUILDMODE_COMMAND + CLI_BUILDMODE_COMMAND, }; struct cli_command { From 8c9f3f8a5dea5456b5068f72489a01bfcdaab1c7 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 17 Jul 2019 13:29:05 -0400 Subject: [PATCH 11/25] Rework of command deletion to fix memory leak/segfault There was at least one corner case in the delete a code pathway that resulted in both a memory leak and traversing a no-longer valid pointer. In 'cli_unregister_all()' cli->commands was never being updated to NULL. Between 'cli_free_command()' and all callers there was no explicit code to ensure that the any command pointing to the command being deleted would point to the command after (if deleteing c, the c->next field). --- libcli.c | 128 ++++++++++++++++++++++++++++++++++++------------------- libcli.h | 4 +- 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/libcli.c b/libcli.c index 60279d8..7db28a3 100644 --- a/libcli.c +++ b/libcli.c @@ -147,7 +147,7 @@ static int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stag static char *cli_int_buildmode_extend_cmdline(char *, char *word); static void cli_int_free_buildmode(struct cli_def *cli); static int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); -static void cli_free_command(struct cli_command *cmd); +static void cli_free_command(struct cli_def *cli, struct cli_command *cmd); static int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type); static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)); static struct cli_command *cli_int_register_buildmode_command( @@ -171,7 +171,7 @@ static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *p static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline); -static struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, +static void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c); static ssize_t _write(int fd, const void *buf, size_t count) { @@ -361,29 +361,46 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) { return old; } -struct cli_command *cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c) { - struct cli_command *p; +void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c) { + struct cli_command *p = NULL; - if (!c) return NULL; + if (!c) return ; + c->parent = parent; + + /* + * Figure it we have a chain, or would be the first element on it + * If we'd be the first element, assign as such. + * Otherwise find the lead element + * so we can trace it below + */ + if (parent) { if (!parent->children) { parent->children = c; } else { - for (p = parent->children; p && p->next; p = p->next) - ; - if (p) p->next = c; + p = parent->children; } } else { if (!cli->commands) { cli->commands = c; } else { - for (p = cli->commands; p && p->next; p = p->next) - ; - if (p) p->next = c; + p = cli->commands; } } - return c; + + /* + * If we have a chain (p is not null), run down to the last element + * and place this command at the end + */ + for (; p && p->next; p = p->next) + ; + + if (p) { + p->next = c; + c->previous = p; + } + return ; } struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command, @@ -401,7 +418,6 @@ struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command return NULL; } - c->parent = parent; c->privilege = privilege; c->mode = mode; if (help && !(c->help = strdup(help))) { @@ -410,25 +426,53 @@ struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command return NULL; } - if (!cli_register_command_core(cli, parent, c)) { - cli_free_command(c); - c = NULL; - } + cli_register_command_core(cli, parent, c); return c; } -static void cli_free_command(struct cli_command *cmd) { +static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { struct cli_command *c, *p; - + for (c = cmd->children; c;) { p = c->next; - cli_free_command(c); + cli_free_command(cli, c); c = p; } free(cmd->command); if (cmd->help) free(cmd->help); if (cmd->optargs) cli_unregister_all_optarg(cmd); + + /* + * Ok, update the pointers of anyone who pointed to us. + * We have 3 pointers to worry about - parent, previous, and next. + * We don't have to worry about children since they've been cleared above. + * If both parent *and* previous being null this means that + * cli->command points to us, so we need to only update + * cli->command to point to next. + * Otherwise ensure that any item before/behind us points + * around us. + * + * Important - there is no provision for deleting a discrete subcommand. + * For example, suppose we define foo, then bar with foo as the parent, then baz with + * bar as the parent. We cannot delete 'bar' and have a new chain of foo -> baz. + * The above freeing of children prevents this in the first place. + */ + + if (!cmd->parent && !cmd->previous) { + cli->commands = cmd->next; + if (cmd->next) { + cmd->next->parent = NULL; + cmd->next->previous = NULL; + } + } else { + if (cmd->previous) { + cmd->previous->next = cmd->next; + } + if (cmd->next) { + cmd->next->previous = cmd->previous; + } + } free(cmd); } @@ -438,17 +482,13 @@ int cli_int_unregister_command_core(struct cli_def *cli, const char *command, in if (!command) return -1; if (!cli->commands) return CLI_OK; - for (c = cli->commands; c; c = c->next) { + for (c = cli->commands; c;) { + p = c->next; if ((strcmp(c->command, command) == 0) && (c->command_type == command_type)) { - if (p) - p->next = c->next; - else - cli->commands = c->next; - - cli_free_command(c); + cli_free_command(cli, c); return CLI_OK; } - p = c; + c = p; } return CLI_OK; @@ -628,14 +668,9 @@ void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int c for (c = command; c;) { p = c->next; if ((c->command_type == command_type) || (command_type == CLI_ANY_COMMAND)) { - // handle case where we're deleting *first* command - if (c == cli->commands) cli->commands = p; + if (c == cli->commands) cli->commands = c->next; // Unregister all child commands - if (c->children) cli_unregister_tree(cli, c->children, command_type); - if (c->optargs) cli_unregister_all_optarg(c); - if (c->command) free(c->command); - if (c->help) free(c->help); - free(c); + cli_free_command(cli, c); } c = p; } @@ -2023,11 +2058,9 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command free(c); return NULL; } + // filters are all registered at the top level - if (!cli_register_command_core(cli, NULL, c)) { - cli_free_command(c); - c = NULL; - } + cli_register_command_core(cli, NULL, c); return c; } @@ -2239,7 +2272,7 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft void cli_int_free_buildmode(struct cli_def *cli) { if (!cli || !cli->buildmode) return; - // cli_unregister_tree(cli, cli->commands, CLI_BUILDMODE_COMMAND); + cli_unregister_tree(cli, cli->commands, CLI_BUILDMODE_COMMAND); cli->mode = cli->buildmode->mode; free_z(cli->buildmode->cname); free_z(cli->buildmode->mode_text); @@ -2351,7 +2384,6 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru } c->command_type = CLI_BUILDMODE_COMMAND; - c->parent = parent; c->privilege = privilege; c->mode = mode; if (help && !(c->help = strdup(help))) { @@ -2359,11 +2391,9 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru free(c); return NULL; } + // buildmode commmands are all registered at the top level - if (!cli_register_command_core(cli, NULL, c)) { - cli_free_command(c); - c = NULL; - } + cli_register_command_core(cli, NULL, c); return c; } @@ -3140,3 +3170,11 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage } return; } + +void cli_unregister_all_commands(struct cli_def *cli) { + cli_unregister_tree(cli, cli->commands, CLI_REGULAR_COMMAND); +} + +void cli_unregister_all_filters(struct cli_def *cli) { + cli_unregister_tree(cli, cli->commands, CLI_FILTER_COMMAND); +} diff --git a/libcli.h b/libcli.h index 9966fba..bc47c54 100644 --- a/libcli.h +++ b/libcli.h @@ -105,6 +105,7 @@ struct cli_command { char *help; int privilege; int mode; + struct cli_command *previous; struct cli_command *next; struct cli_command *children; struct cli_command *parent; @@ -239,8 +240,9 @@ char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after); struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); int cli_unregister_optarg(struct cli_command *cmd, const char *name); char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); -void cli_unregister_all(struct cli_def *cli, struct cli_command *command); void cli_unregister_all_optarg(struct cli_command *c); +void cli_unregister_all_filters(struct cli_def *cli); +void cli_unregister_all_commands(struct cli_def *cli); #ifdef __cplusplus } From a217af07c5510e74ad442a7830deaf39e841f063 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 17 Jul 2019 14:47:37 -0400 Subject: [PATCH 12/25] Revise logic in cli_free_command for simplicity --- libcli.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libcli.c b/libcli.c index 7db28a3..9cbaf88 100644 --- a/libcli.c +++ b/libcli.c @@ -447,9 +447,8 @@ static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { * Ok, update the pointers of anyone who pointed to us. * We have 3 pointers to worry about - parent, previous, and next. * We don't have to worry about children since they've been cleared above. - * If both parent *and* previous being null this means that - * cli->command points to us, so we need to only update - * cli->command to point to next. + * If both cli->command points to us we need to update + * cli->command to point to whatever command is 'next'. * Otherwise ensure that any item before/behind us points * around us. * @@ -459,7 +458,7 @@ static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { * The above freeing of children prevents this in the first place. */ - if (!cmd->parent && !cmd->previous) { + if (cmd == cli->commands ) { cli->commands = cmd->next; if (cmd->next) { cmd->next->parent = NULL; From 3dec4df3f47abe069fe70b7c92ec8b22263285ca Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 17 Jul 2019 16:59:16 -0400 Subject: [PATCH 13/25] Restore 'cli_unregister_all()' command. Calls new cli_int_unregister_tree(), but preserves legacy semantics of commands only --- libcli.c | 7 ++++++- libcli.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libcli.c b/libcli.c index 9cbaf88..75d2953 100644 --- a/libcli.c +++ b/libcli.c @@ -663,7 +663,7 @@ void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int c struct cli_command *c, *p = NULL; if (!command) command = cli->commands; - + for (c = command; c;) { p = c->next; if ((c->command_type == command_type) || (command_type == CLI_ANY_COMMAND)) { @@ -675,6 +675,11 @@ void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int c } } + +void cli_unregister_all(struct cli_def *cli, struct cli_command *command) { + cli_unregister_tree(cli, command, CLI_REGULAR_COMMAND); +} + int cli_done(struct cli_def *cli) { if (!cli) return CLI_OK; struct unp *u = cli->users, *n; diff --git a/libcli.h b/libcli.h index bc47c54..94fb151 100644 --- a/libcli.h +++ b/libcli.h @@ -243,6 +243,7 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft void cli_unregister_all_optarg(struct cli_command *c); void cli_unregister_all_filters(struct cli_def *cli); void cli_unregister_all_commands(struct cli_def *cli); +void cli_unregister_all(struct cli_def *cli, struct cli_command *command) ; #ifdef __cplusplus } From 4472281d9b4f2b609c57735e82bfe4d6b44c6142 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Thu, 18 Jul 2019 08:25:34 -0400 Subject: [PATCH 14/25] Additional stylistic changes --- libcli.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libcli.c b/libcli.c index 75d2953..f6ddb01 100644 --- a/libcli.c +++ b/libcli.c @@ -2329,20 +2329,20 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag NULL); } else { rc = CLI_BUILDMODE_ERROR; - goto out; + goto out; } } else { if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_multiple_cback, optarg->privilege, cli->mode, optarg->help)) { rc = CLI_BUILDMODE_ERROR; - goto out; + goto out; } } else { if (!cli_int_register_buildmode_command(cli, NULL, optarg->name, cli_int_buildmode_flag_cback, optarg->privilege, cli->mode, optarg->help)) { rc = CLI_BUILDMODE_ERROR; - goto out; + goto out; } } } @@ -2361,7 +2361,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); out: - if (rc!=CLI_BUILDMODE_START) { + if (rc != CLI_BUILDMODE_START) { cli_int_free_buildmode(cli); } return rc; From 65d21d79d94f991e3086f5fffff7d4ecf734cfbb Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Thu, 18 Jul 2019 17:04:42 -0400 Subject: [PATCH 15/25] Additional (missed) style consistency edit --- libcli.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libcli.c b/libcli.c index f6ddb01..80dbd7c 100644 --- a/libcli.c +++ b/libcli.c @@ -2172,9 +2172,8 @@ int cli_unregister_optarg(struct cli_command *cmd, const char *name) { struct cli_optarg *lastptr; int retval = CLI_ERROR; // iterate looking for this option name, stopping at end or if name matches - for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next) { + for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next) ; - } // if ptr, then we found the optarg to delete if (ptr) { From 96c7e7f40b726921f7ac1413966d2161769d55a1 Mon Sep 17 00:00:00 2001 From: David Parrish Date: Fri, 19 Jul 2019 10:30:11 +1000 Subject: [PATCH 16/25] Add documentation from the old sites page --- README => README.md | 87 +++++++------- clitest.c | 8 +- doc/developers-guide.md | 243 ++++++++++++++++++++++++++++++++++++++++ libcli.c | 55 ++++----- libcli.h | 2 +- 5 files changed, 315 insertions(+), 80 deletions(-) rename README => README.md (56%) create mode 100644 doc/developers-guide.md diff --git a/README b/README.md similarity index 56% rename from README rename to README.md index a39e115..4e8b57b 100644 --- a/README +++ b/README.md @@ -1,112 +1,111 @@ -libcli +Libcli provides a shared C library for including a Cisco-like command-line +interface into other software. -libcli emulates a cisco style telnet command-line interface. +It’s a telnet interface which supports command-line editing, history, +authentication and callbacks for a user-definable function tree. To compile: - make - make install +```sh +$ make +$ make install +``` -This will install libcli.so into /usr/local/lib. If you want to change -the location, edit Makefile. +This will install `libcli.so` into `/usr/local/lib`. If you want to change the +location, edit Makefile. -There is a test application built called clitest. Run this and telnet -to port 8000. +There is a test application built called clitest. Run this and telnet to port +8000. By default, a single username and password combination is enabled. +``` Username: fred Password: nerk +``` -Get help by entering "help" or hitting ?. +Get help by entering `help` or hitting `?`. libcli provides support for using the arrow keys for command-line editing. Up and Down arrows will cycle through the command history, and Left & Right can be used for editing the current command line. + libcli also works out the shortest way of entering a command, so if you have a -command "show users grep foobar" defined, you can enter "sh us g foobar" if that +command `show users | grep foobar` defined, you can enter `sh us | g foobar` if that is the shortest possible way of doing it. -Enter "sh?" at the command line to get a list of commands starting with "sh" +Enter `sh?` at the command line to get a list of commands starting with `sh` A few commands are defined in every libcli program: -help -quit -exit -logout -history - - +* `help` +* `quit` +* `exit` +* `logout` +* `history` Use in your own code: -First of all, make sure you #include in your C code, and link -with -lcli. +First of all, make sure you `#include ` in your C code, and link with +`-lcli`. If you have any trouble with this, have a look at clitest.c for a demonstration. -Start your program off with a cli_init(). +Start your program off with a `cli_init()`. This sets up the internal data structures required. When a user connects, they are presented with a greeting if one is set using the -cli_set_banner(banner) function. - +`cli_set_banner(banner)` function. By default, the command-line session is not authenticated, which means users will get full access as soon as they connect. As this may not be always the best thing, 2 methods of authentication are available. First, you can add username / password combinations with the -cli_allow_user(username, password) function. When a user connects, they can +`cli_allow_user(username, password)` function. When a user connects, they can connect with any of these username / password combinations. -Secondly, you can add a callback using the cli_set_auth_callback(callback) -function. This function is passed the username and password as char *, and must -return CLI_OK if the user is to have access and CLI_ERROR if they are not. +Secondly, you can add a callback using the `cli_set_auth_callback(callback)` +function. This function is passed the username and password as `char *`, and must +return `CLI_OK` if the user is to have access and `CLI_ERROR` if they are not. The library itself will take care of prompting the user for credentials. - - - Commands are built using a tree-like structure. You define commands with the -cli_register_command(parent, command, callback, privilege, mode, help) function. +`cli_register_command(parent, command, callback, privilege, mode, help)` function. -parent is a cli_command * reference to a previously added command. Using a +`parent` is a `cli_command *` reference to a previously added command. Using a parent you can build up complex commands. -e.g. to provide commands "show users", "show sessions" and "show people", use + +e.g. to provide commands `show users`, `show sessions` and `show people`, use the following sequence: +```c cli_command *c = cli_register_command(NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(c, "sessions", fn_sessions, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the sessions connected"); cli_register_command(c, "users", fn_users, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the users connected"); cli_register_command(c, "people", fn_people, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show a list of the people I like"); +``` - -If callback is NULL, the command can be used as part of a tree, but cannot be +If callback is `NULL`, the command can be used as part of a tree, but cannot be individually run. - If you decide later that you don't want a command to be run, you can call -cli_unregister_command(command). +`cli_unregister_command(command)`. You can use this to build dynamic command trees. - It is possible to carry along a user-defined context to all command callbacks -using cli_set_context(cli, context) and cli_get_context(cli) functions. +using `cli_set_context(cli, context)` and `cli_get_context(cli)` functions. You are responsible for accepting a TCP connection, and for creating a process or thread to run the cli. Once you are ready to process the -connection, call cli_loop(cli, sock) to interact with the user on the +connection, call `cli_loop(cli, sock)` to interact with the user on the given socket. This function will return when the user exits the cli, either by breaking the -connection or entering "quit". - -Call cli_done() to free the data structures. +connection or entering `quit`. +Call `cli_done()` to free the data structures. -- David Parrish (david@dparrish.com) diff --git a/clitest.c b/clitest.c index 36102dd..6e38d88 100644 --- a/clitest.c +++ b/clitest.c @@ -260,7 +260,7 @@ int shape_transient_eval(struct cli_def *cli, const char *name, const char *valu return CLI_OK; } cli_error(cli, "unrecognized value for setting %s -> %s", name, value); - return CLI_ERROR; + return CLI_ERROR; } const char *KnownColors[] = {"black", "white", "gray", "red", "blue", @@ -319,12 +319,12 @@ void run_child(int x) { cli_set_hostname(cli, "router"); cli_telnet_protocol(cli, 1); cli_regular(cli, regular_callback); - + // change regular update to 5 seconds rather than default of 1 second cli_regular_interval(cli, 5); - + // set 60 second idle timeout - cli_set_idle_timeout_callback(cli, 60, idle_timeout); + cli_set_idle_timeout_callback(cli, 60, idle_timeout); cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); diff --git a/doc/developers-guide.md b/doc/developers-guide.md new file mode 100644 index 0000000..6f3b0de --- /dev/null +++ b/doc/developers-guide.md @@ -0,0 +1,243 @@ +## Introduction +libcli provides a telnet command-line environment which can be embedded in other programs. This environment includes useful features such as automatic authentication, history, and command-line editing. + +This guide should show you everything you need to embed libcli into your program. If you have any corrections, suggestions or modifications, please email [David Parrish](mailto:david+libcli@dparrish.com). + +## Authentication +Two methods of authentcation are supported by libcli - internal and callback. + +Internal authentication is based on a list of username / password combinations that are set up before `cli_loop()` is called. Passwords may be clear text, MD5 encrypted, or DES encrypted (requires a `{crypt}` prefix to distinguish from clear text). + +Callback based authentication calls a callback with the username and password that the user enters, and must return either `CLI_OK` or `CLI_ERROR`. This can be used for checking passwords against some other database such as LDAP. + +If neither `cli_set_auth_callback()` or `cli_allow_user()` have been called before `cli_loop()`, then authentication will be disabled and the user will not be prompted for a username / password combination. + +Authentication for the privileged state can also be defined by a static password or by a callback. Use the `cli_set_enable_callback()` or `cli_allow_enable()` functions to set the enable password. + +## Tutorial +This section will guide you through implementing libcli in a basic server. + +### Create a file libclitest.c + +```c +#include + +int main(int argc, char *argv[]) { + struct sockaddr_in servaddr; + struct cli_command *c; + struct cli_def *cli; + int on = 1, x, s; + + // Must be called first to setup data structures + cli = cli_init(); + + // Set the hostname (shown in the the prompt) + cli_set_hostname(cli, "test"); + + // Set the greeting + cli_set_banner(cli, "Welcome to the CLI test program."); + + // Enable 2 username / password combinations + cli_allow_user(cli, "fred", "nerk"); + cli_allow_user(cli, "foo", "bar"); + + // Set up a few simple one-level commands + cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, NULL, "simple", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + + // This command takes arguments, and requires privileged mode (enable) + cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); + + // Set up 2 commands "show counters" and "show junk" + c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + // Note how we store the previous command and use it as the parent for this one. + cli_register_command(cli, c, "junk", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + // This one has some help text + cli_register_command(cli, c, "counters", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the counters that the system uses"); + + // Create a socket + s = socket(AF_INET, SOCK_STREAM, 0); + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + // Listen on port 12345 + memset(&servaddr, 0, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(12345); + bind(s, (struct sockaddr *)&servaddr, sizeof(servaddr)); + + // Wait for a connection + listen(s, 50); + + while ((x = accept(s, NULL, 0))) { + // Pass the connection off to libcli + cli_loop(cli, x); + close(x); + } + + // Free data structures + cli_done(cli); + + return 0; +} +``` + +This code snippet is all that's required to enable a libcli program. However it's not yet compilable because we haven't created the callback functions. + +A few commands have been created: + +* `test` +* `simple` +* `set` +* `show junk` +* `show counters` + +Note that `simon` isn't on this list because `callback` was `NULL` when it was registered, so the command will not be available. + +Also, the standard libcli commands `help`, `exit`, `logout`, `quit` and `history` are also available automatically. + +Make this program complete by adding the callback functions + +```c +int cmd_test(struct cli_def *cli, char *command, char *argv[], int argc) { + cli_print(cli, "called %s with %s\r\n", __FUNCTION__, command); + return CLI_OK; +} + +int cmd_set(struct cli_def *cli, char *command, char *argv[], int argc) { + if (argc < 2) { + cli_print(cli, "Specify a variable to set\r\n"); + return CLI_OK; + } + cli_print(cli, "Setting %s to %s\r\n", argv[0], argv[1]); + return CLI_OK; +} +``` + +2 callback functions are defined here, `cmd_test()` and `cmd_set()`. `cmd_test()` is called by many of the commands defined in the tutorial, although in reality you would usually use a callback for a single command. + +`cmd_test()` simply echos the command entered back to the client. Note that it shows the full expanded command, so you can enter "`te`" at the prompt and it will print back "`called with test`". + +`cmd_set()` handles the arguments given on the command line. This allows you to use a single callback to handle lots of arguments like: + +* `set colour green` +* `set name David` +* `set email "I don't have an e-mail address"` +* etc... + +Compile the code + +```sh +gcc libclitest.c -o libclitest -lcli +``` + +You can now run the program with `./libclitest` and telnet to port 12345 to see your work in action. + +## Function Reference + +### cli\_init() +This must be called before any other cli_xxx function. It sets up the internal data structures used for command-line processing. + +Returns a `struct cli_def *` which must be passed to all other `cli_xxx` functions. + +### cli\_done(struct cli\_def \*cli) +This is optional, but it's a good idea to call this when you are finished with libcli. This frees memory used by libcli. + +### cli\_register\_command(struct cli\_def \*cli, struct cli\_command *parent, char *command, int (*callback)(struct cli\_def *, char *, char **, int), int privilege, int mode, char *help) +Add a command to the internal command tree. Returns a `struct cli_command *`, which you can pass as parent to another call to `cli_register_command()`. + +When the command has been entered by the user, callback is checked. If it is not `NULL`, then the callback is called with: + +`struct cli_def *` - the handle of the cli structure. This must be passed to all cli functions, including `cli_print()`. +`char *` - the entire command which was entered. This is after command expansion. +`char **` - the list of arguments entered +`int` - the number of arguments entered +The callback must return `CLI_OK` if the command was successful, `CLI_ERROR` if processing wasn't successful and the next matching command should be tried (if any), or `CLI_QUIT` to drop the connection (e.g. on a fatal error). + +If parent is `NULL`, the command is added to the top level of commands, otherwise it is a subcommand of parent. + +privilege should be set to either PRIVILEGE\_PRIVILEGED or PRIVILEGE\_UNPRIVILEGED. If set to PRIVILEGE\_PRIVILEGED then the user must have entered enable before running this command. + +`mode` should be set to `MODE_EXEC` for no configuration mode, `MODE_CONFIG` for generic configuration commands, or your own config level. The user can enter the generic configuration level by entering configure terminal, and can return to `MODE_EXEC` by entering exit or CTRL-Z. You can define commands to enter your own configuration levels, which should call the `cli_set_configmode()` function. + +If help is provided, it is given to the user when the use the help command or press ?. + +### cli\_unregister\_command(struct cli\_def \*cli, char *command) +Remove a command command and all children. There is not provision yet for removing commands at lower than the top level. + +### cli\_loop(struct cli\_def \*cli, int sockfd) +The main loop of the command-line environment. This must be called with the FD of a socket open for bi-directional communication (sockfd). + +### cli\_loop() handles the telnet negotiation and authentication. It returns only when the connection is finished, either by a server or client disconnect. +Returns `CLI_OK`. + +### cli\_set\_auth\_callback(struct cli\_def \*cli, int (*auth\_callback)(char *, char *)) +Enables or disables callback based authentication. + +If `auth_callback` is not `NULL`, then authentication will be required on connection. `auth_callback` will be called with the username and password that the user enters. + +`auth_callback` must return a non-zero value if authentication is successful. + +If `auth_callback` is `NULL`, then callback based authentication will be disabled. + +### cli\_allow\_user(struct cli\_def \*cli, char *username, char *password) +Enables internal authentication, and adds username/password to the list of allowed users. + +The internal list of users will be checked before callback based authentication is tried. + +### cli\_deny\_user(struct cli\_def \*cli, char *username) +Removes username/password from the list of allowed users. + +If this is the last combination in the list, then internal authentication will be disabled. + +### cli\_set\_banner(struct cli\_def \*cli, char *banner) +Sets the greeting that clients will be presented with when they connect. This may be a security warning for example. + +If this function is not called or called with a `NULL` argument, no banner will be presented. + +### cli\_set\_hostname(struct cli\_def \*cli, char *hostname) +Sets the hostname to be displayed as the first part of the prompt. + +### cli\_regular(struct cli\_def \*cli, int(*callback)(struct cli\_def *)) +Adds a callback function which will be called every second that a user is connected to the cli. This can be used for regular processing such as debugging, time counting or implementing idle timeouts. + +Pass `NULL` as the callback function to disable this at runtime. + +If the callback function does not return `CLI_OK`, then the user will be disconnected. + +### cli\_file(struct cli\_def \*cli, FILE *f, int privilege, int mode) +This reads and processes every line read from f as if it were entered at the console. The privilege level will be set to privilege and mode set to mode during the processing of the file. + +### cli\_print(struct cli\_def \*cli, char *format, ...) +This function should be called for any output generated by a command callback. + +It takes a printf() style format string and a variable number of arguments. + +Be aware that any output generated by `cli_print()` will be passed through any filter currently being applied, and the output will be redirected to the `cli_print_callback()` if one has been specified. + +### cli\_error(struct cli\_def \*cli, char *format, ...) +A variant of `cli_print()` which does not have filters applied. + +### cli\_print\_callback(struct cli\_def \*cli, void (*callback)(struct cli\_def *, char *)) +Whenever `cli_print()` or `cli_error()` is called, the output generally goes to the user. If you specify a callback using this function, then the output will be sent to that callback. The function will be called once for each line, and it will be passed a single null-terminated string, without any newline characters. + +Specifying `NULL` as the callback parameter will make libcli use the default `cli_print()` function. + +### cli\_set\_enable\_callback(struct cli\_def \*cli, void (*callback)(struct cli\_def *, char *)) +Just like `cli_set_auth_callback, this takes a pointer to a callback function to authorize privileged access. However this callback only takes a single string - the password. + +### cli\_allow\_enable(struct cli\_def \*cli, char *password) +This will allow a static password to be used for the enable command. This static password will be checked before running any enable callbacks. + +Set this to `NULL` to not have a static enable password. + +### cli\_set\_configmode(struct cli\_def \*cli, int mode, char *string) +This will set the configuration mode. Once set, commands will be restricted to only ones in the selected configuration mode, plus any set to `MODE_ANY`. The previous mode value is returned. + +The string passed will be used to build the prompt in the set configuration mode. e.g. if you set the string `test`, the prompt will become: + +``` +hostname(config-test)# +``` + diff --git a/libcli.c b/libcli.c index 80dbd7c..416b839 100644 --- a/libcli.c +++ b/libcli.c @@ -165,14 +165,13 @@ static int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *na static int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value); static int cli_int_execute_buildmode(struct cli_def *cli); static void cli_int_free_found_optargs(struct cli_optarg_pair **optarg_pair); -static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name ) ; +static void cli_int_unset_optarg_value(struct cli_def *cli, const char *name); static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command); static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline); -static void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, - struct cli_command *c); +static void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c); static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; @@ -364,17 +363,17 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) { void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c) { struct cli_command *p = NULL; - if (!c) return ; + if (!c) return; c->parent = parent; - + /* * Figure it we have a chain, or would be the first element on it * If we'd be the first element, assign as such. * Otherwise find the lead element * so we can trace it below */ - + if (parent) { if (!parent->children) { parent->children = c; @@ -388,19 +387,19 @@ void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, p = cli->commands; } } - + /* * If we have a chain (p is not null), run down to the last element * and place this command at the end */ for (; p && p->next; p = p->next) - ; - + ; + if (p) { p->next = c; c->previous = p; } - return ; + return; } struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command, @@ -432,7 +431,7 @@ struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { struct cli_command *c, *p; - + for (c = cmd->children; c;) { p = c->next; cli_free_command(cli, c); @@ -442,23 +441,23 @@ static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { free(cmd->command); if (cmd->help) free(cmd->help); if (cmd->optargs) cli_unregister_all_optarg(cmd); - + /* * Ok, update the pointers of anyone who pointed to us. * We have 3 pointers to worry about - parent, previous, and next. * We don't have to worry about children since they've been cleared above. * If both cli->command points to us we need to update - * cli->command to point to whatever command is 'next'. + * cli->command to point to whatever command is 'next'. * Otherwise ensure that any item before/behind us points - * around us. + * around us. * * Important - there is no provision for deleting a discrete subcommand. - * For example, suppose we define foo, then bar with foo as the parent, then baz with + * For example, suppose we define foo, then bar with foo as the parent, then baz with * bar as the parent. We cannot delete 'bar' and have a new chain of foo -> baz. - * The above freeing of children prevents this in the first place. + * The above freeing of children prevents this in the first place. */ - - if (cmd == cli->commands ) { + + if (cmd == cli->commands) { cli->commands = cmd->next; if (cmd->next) { cmd->next->parent = NULL; @@ -467,7 +466,7 @@ static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { } else { if (cmd->previous) { cmd->previous->next = cmd->next; - } + } if (cmd->next) { cmd->next->previous = cmd->previous; } @@ -663,7 +662,7 @@ void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int c struct cli_command *c, *p = NULL; if (!command) command = cli->commands; - + for (c = command; c;) { p = c->next; if ((c->command_type == command_type) || (command_type == CLI_ANY_COMMAND)) { @@ -675,7 +674,6 @@ void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int c } } - void cli_unregister_all(struct cli_def *cli, struct cli_command *command) { cli_unregister_tree(cli, command, CLI_REGULAR_COMMAND); } @@ -893,7 +891,6 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar i++; stage->first_unmatched = i; if (c->optargs) { - cli_int_parse_optargs(cli, stage, c, lastchar, comphelp); } else if (lastchar == '?') { // special case for getting help with no defined optargs.... @@ -2062,7 +2059,7 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command free(c); return NULL; } - + // filters are all registered at the top level cli_register_command_core(cli, NULL, c); return c; @@ -2258,7 +2255,6 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft struct cli_optarg_pair *optarg_pair; for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair = optarg_pair->next) { - // check next entry if this isn't our name if (strcasecmp(optarg_pair->name, name)) continue; @@ -2288,7 +2284,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag struct cli_command *c; struct cli_buildmode *buildmode; int rc = CLI_BUILDMODE_START; - + if (!cli || !(buildmode = (struct cli_buildmode *)calloc(1, sizeof(struct cli_buildmode)))) { cli_error(cli, "Unable to build buildmode mode for command %s", stage->command->command); rc = CLI_BUILDMODE_ERROR; @@ -2327,8 +2323,8 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag optarg->privilege, cli->mode, optarg->help, optarg->get_completions, optarg->validator, NULL); } else { - rc = CLI_BUILDMODE_ERROR; - goto out; + rc = CLI_BUILDMODE_ERROR; + goto out; } } else { if (optarg->flags & CLI_CMD_OPTION_MULTIPLE) { @@ -2667,7 +2663,6 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma CORRECT_CHECKS: if (rc == CLI_OK) { - stage->command = c; stage->first_unmatched = start_word + 1; stage->first_optarg = stage->first_unmatched; @@ -2703,7 +2698,6 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma } int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { - int i; int rc = CLI_OK; int command_type; @@ -2969,7 +2963,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage int c_idx; // candidate_idx char *value; int is_last_word = 0; - int (*validator)(struct cli_def *, const char * name, const char * value); + int (*validator)(struct cli_def *, const char *name, const char *value); /* * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to @@ -3068,7 +3062,6 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage } } - // set some values for use later - makes code much easier to read value = stage->words[w_idx]; oaptr = candidates[0]; diff --git a/libcli.h b/libcli.h index 94fb151..f2c6022 100644 --- a/libcli.h +++ b/libcli.h @@ -243,7 +243,7 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft void cli_unregister_all_optarg(struct cli_command *c); void cli_unregister_all_filters(struct cli_def *cli); void cli_unregister_all_commands(struct cli_def *cli); -void cli_unregister_all(struct cli_def *cli, struct cli_command *command) ; +void cli_unregister_all(struct cli_def *cli, struct cli_command *command); #ifdef __cplusplus } From b50a5d3365db31aae978ee839b3d51fe674339e5 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 19 Jul 2019 09:23:26 -0400 Subject: [PATCH 17/25] Add 'CLI_CMD_SPOT_CHECK as a new optarg type CLI_CMD_SPOT_CHECK is designed to allow a 'spot check' to ensure that the optargs seen so far are 'sane' to allow parsing to continue. The check will *only* be done if there are no potential candidates at the point where the spot check is being evaluated. The recommended place to put any spot check is immediately prior to any command that would need 'sane' arguments to continue. --- clitest.c | 25 +++++++++++++++++++++++-- libcli.c | 11 +++++++++-- libcli.h | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/clitest.c b/clitest.c index 6e38d88..834e539 100644 --- a/clitest.c +++ b/clitest.c @@ -304,6 +304,24 @@ int side_length_validator(struct cli_def *cli, const char *name, const char *val return rc; } +int check1_validator(struct cli_def *cli, UNUSED(const char *name), UNUSED(const char *value)) { + char *color; + char *transparent; + + printf("check1_validator called \n"); + color = cli_get_optarg_value(cli, "color", NULL); + transparent = cli_get_optarg_value(cli, "transparent", NULL); + + if (!color && !transparent) { + cli_error(cli,"\nMust supply either a color or transparent!"); + return CLI_ERROR; + } else if (color && !strcmp(color,"black") && transparent) { + cli_error(cli, "\nCan not have a transparent black object!"); + return CLI_ERROR; + } + return CLI_OK; +} + void run_child(int x) { struct cli_command *c; struct cli_def *cli; @@ -352,11 +370,14 @@ void run_child(int x) { "Set transparent flag", NULL, NULL, NULL); cli_register_optarg(c, "verbose", CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTION_MULTIPLE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set transparent flag", NULL, NULL, NULL); + cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", + color_completor, color_validator, NULL); + cli_register_optarg(c, "__check1__", CLI_CMD_SPOT_CHECK,PRIVILEGE_UNPRIVILEGED, MODE_EXEC, + NULL, NULL, check1_validator, + NULL); cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape to calclate perimeter for", shape_completor, shape_validator, shape_transient_eval); - cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", - color_completor, color_validator, NULL); cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 1 length", NULL, side_length_validator, NULL); cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_RECTANGLE, diff --git a/libcli.c b/libcli.c index 416b839..48693b5 100644 --- a/libcli.c +++ b/libcli.c @@ -3000,7 +3000,14 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a candidate * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are required */ - if (stage->words[w_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) && + if ((oaptr->flags & CLI_CMD_SPOT_CHECK) && (num_candidates==0)) { + stage->status = (*oaptr->validator)(cli, NULL, NULL); + if (stage->status != CLI_OK) { + stage->error_word = stage->words[w_idx]; + cli_reprompt(cli); + return; + } + } else if (stage->words[w_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) && !strcmp(oaptr->name, stage->words[w_idx])) { candidates[0] = oaptr; num_candidates = 1; @@ -3035,7 +3042,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage /* * So now we could have one or more candidates. We need to call get help/completions *only* if this is the * 'last-word' - * Remember that last word for optinal arguments is last or next to last.... + * Remember that last word for optional arguments is last or next to last.... */ if (lastchar != '\0') { int called_comphelp = 0; diff --git a/libcli.h b/libcli.h index f2c6022..73cafa7 100644 --- a/libcli.h +++ b/libcli.h @@ -132,6 +132,7 @@ enum optarg_flags { CLI_CMD_DO_NOT_RECORD = 1 << 7, CLI_CMD_REMAINDER_OF_LINE = 1 << 8, CLI_CMD_HYPHENATED_OPTION = 1 << 9, + CLI_CMD_SPOT_CHECK = 1 << 10, }; struct cli_optarg { From b8f3b3d835c9693ebbb53406d8f2c6272ccd5183 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 19 Jul 2019 09:39:12 -0400 Subject: [PATCH 18/25] Fix formatting slightly --- libcli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcli.c b/libcli.c index 48693b5..edefade 100644 --- a/libcli.c +++ b/libcli.c @@ -3000,7 +3000,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a candidate * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are required */ - if ((oaptr->flags & CLI_CMD_SPOT_CHECK) && (num_candidates==0)) { + if ((oaptr->flags & CLI_CMD_SPOT_CHECK) && (num_candidates == 0)) { stage->status = (*oaptr->validator)(cli, NULL, NULL); if (stage->status != CLI_OK) { stage->error_word = stage->words[w_idx]; From 812ec50368249395e634b2137141756369d18789 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 19 Jul 2019 09:48:14 -0400 Subject: [PATCH 19/25] Add 'show'/'unset' to restricted buildmode commands, update error message if a conflict with an optarg found --- libcli.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libcli.c b/libcli.c index edefade..fae25e9 100644 --- a/libcli.c +++ b/libcli.c @@ -2307,9 +2307,10 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag // build new *limited* list of commands from this commands optargs for (optarg = stage->command->optargs; optarg; optarg = optarg->next) { - // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel - if (!strcmp(optarg->name, "cancel") || (!strcmp(optarg->name, "exit"))) { - cli_error(cli, "Unable to build buildmode mode from optarg named %s", optarg->name); + // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel/show/unset + if (!strcmp(optarg->name, "cancel") || (!strcmp(optarg->name, "exit")) || + !strcmp(optarg->name, "show") || (!strcmp(optarg->name, "unset"))) { + cli_error(cli, "Default buildmode command conflicts with optarg named %s", optarg->name); rc = CLI_BUILDMODE_ERROR; goto out; } @@ -2343,7 +2344,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag } } cli->buildmode->cname = strdup(cli_command_name(cli, stage->command)); - // and lastly two 'always there' commands to cancel current mode and to execute the command + // and lastly four 'always there' commands to cancel current mode and to execute the command, show settings, and unset cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); cli_int_register_buildmode_command(cli, NULL, "exit", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, From 57c2fec5bbfd26d4c630c6ab4e6bdedbfe5a48c1 Mon Sep 17 00:00:00 2001 From: David Parrish Date: Sat, 20 Jul 2019 18:45:29 +1000 Subject: [PATCH 20/25] Tidy up comments and remove extraneous parentheses --- .clang-tidy | 34 ++++ .gitignore | 1 + Makefile | 4 + clitest.c | 13 +- libcli.c | 567 ++++++++++++++++++++++++---------------------------- 5 files changed, 305 insertions(+), 314 deletions(-) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..bf2039d --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,34 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +User: dparrish +CheckOptions: + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' +... + diff --git a/.gitignore b/.gitignore index b0c2baf..b2e1526 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.so *.so.* clitest +compile_commands.json diff --git a/Makefile b/Makefile index f655222..b944720 100644 --- a/Makefile +++ b/Makefile @@ -85,3 +85,7 @@ rpmprep: rpm: rpmprep rpmbuild -ta libcli-$(MAJOR).$(MINOR).$(REVISION).tar.gz --define "debug_package %{nil}" --clean + +lint: + clang-tidy -quiet -warnings-as-errors *.c *.h + diff --git a/clitest.c b/clitest.c index 834e539..afb52bd 100644 --- a/clitest.c +++ b/clitest.c @@ -307,15 +307,15 @@ int side_length_validator(struct cli_def *cli, const char *name, const char *val int check1_validator(struct cli_def *cli, UNUSED(const char *name), UNUSED(const char *value)) { char *color; char *transparent; - + printf("check1_validator called \n"); color = cli_get_optarg_value(cli, "color", NULL); transparent = cli_get_optarg_value(cli, "transparent", NULL); - + if (!color && !transparent) { - cli_error(cli,"\nMust supply either a color or transparent!"); + cli_error(cli, "\nMust supply either a color or transparent!"); return CLI_ERROR; - } else if (color && !strcmp(color,"black") && transparent) { + } else if (color && !strcmp(color, "black") && transparent) { cli_error(cli, "\nCan not have a transparent black object!"); return CLI_ERROR; } @@ -372,9 +372,8 @@ void run_child(int x) { "Set transparent flag", NULL, NULL, NULL); cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", color_completor, color_validator, NULL); - cli_register_optarg(c, "__check1__", CLI_CMD_SPOT_CHECK,PRIVILEGE_UNPRIVILEGED, MODE_EXEC, - NULL, NULL, check1_validator, - NULL); + cli_register_optarg(c, "__check1__", CLI_CMD_SPOT_CHECK, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, + check1_validator, NULL); cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape to calclate perimeter for", shape_completor, shape_validator, shape_transient_eval); diff --git a/libcli.c b/libcli.c index fae25e9..d2b388a 100644 --- a/libcli.c +++ b/libcli.c @@ -1,3 +1,5 @@ +// vim:sw=2 tw=120 et + #ifdef WIN32 #include #include @@ -21,8 +23,6 @@ #endif #include "libcli.h" -// vim:sw=4 tw=120 et - #ifdef __GNUC__ #define UNUSED(d) d __attribute__((unused)) #else @@ -33,10 +33,7 @@ #define MATCH_INVERT 2 #ifdef WIN32 -/* - * Stupid windows has multiple namespaces for filedescriptors, with different - * read/write functions required for each .. - */ +// Stupid windows has multiple namespaces for filedescriptors, with different read/write functions required for each .. int read(int fd, void *buf, unsigned int count) { return recv(fd, buf, count, 0); } @@ -49,7 +46,7 @@ int vasprintf(char **strp, const char *fmt, va_list args) { int size; va_list argCopy; - // do initial vsnprintf on a copy of the va_list + // Do initial vsnprintf on a copy of the va_list va_copy(argCopy, args); size = vsnprintf(NULL, 0, fmt, argCopy); va_end(argCopy); @@ -90,9 +87,7 @@ int fprintf(FILE *stream, const char *fmt, ...) { return size; } -/* - * Dummy definitions to allow compilation on Windows - */ +// Dummy definitions to allow compilation on Windows int regex_dummy() { return 0; }; @@ -124,7 +119,7 @@ struct cli_filter_cmds { const char *help; }; -/* free and zero (to avoid double-free) */ +// Free and zero (to avoid double-free) #define free_z(p) \ do { \ if (p) { \ @@ -170,7 +165,7 @@ static struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const static int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline); -static struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline); +static void cli_int_free_pipeline(struct cli_pipeline *pipeline); static void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, struct cli_command *c); static ssize_t _write(int fd, const void *buf, size_t count) { @@ -368,10 +363,9 @@ void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, c->parent = parent; /* - * Figure it we have a chain, or would be the first element on it + * Figure out we have a chain, or would be the first element on it. * If we'd be the first element, assign as such. - * Otherwise find the lead element - * so we can trace it below + * Otherwise find the lead element so we can trace it below. */ if (parent) { @@ -389,8 +383,7 @@ void cli_register_command_core(struct cli_def *cli, struct cli_command *parent, } /* - * If we have a chain (p is not null), run down to the last element - * and place this command at the end + * If we have a chain (p is not null), run down to the last element and place this command at the end */ for (; p && p->next; p = p->next) ; @@ -446,14 +439,12 @@ static void cli_free_command(struct cli_def *cli, struct cli_command *cmd) { * Ok, update the pointers of anyone who pointed to us. * We have 3 pointers to worry about - parent, previous, and next. * We don't have to worry about children since they've been cleared above. - * If both cli->command points to us we need to update - * cli->command to point to whatever command is 'next'. - * Otherwise ensure that any item before/behind us points - * around us. + * If both cli->command points to us we need to update cli->command to point to whatever command is 'next'. + * Otherwise ensure that any item before/behind us points around us. * * Important - there is no provision for deleting a discrete subcommand. - * For example, suppose we define foo, then bar with foo as the parent, then baz with - * bar as the parent. We cannot delete 'bar' and have a new chain of foo -> baz. + * For example, suppose we define foo, then bar with foo as the parent, then baz with bar as the parent. We cannot + * delete 'bar' and have a new chain of foo -> baz. * The above freeing of children prevents this in the first place. */ @@ -482,7 +473,7 @@ int cli_int_unregister_command_core(struct cli_def *cli, const char *command, in for (c = cli->commands; c;) { p = c->next; - if ((strcmp(c->command, command) == 0) && (c->command_type == command_type)) { + if (strcmp(c->command, command) == 0 && c->command_type == command_type) { cli_free_command(cli, c); return CLI_OK; } @@ -514,11 +505,11 @@ int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char if (cli->privilege == PRIVILEGE_PRIVILEGED) return CLI_OK; if (!cli->enable_password && !cli->enable_callback) { - /* no password required, set privilege immediately */ + // No password required, set privilege immediately. cli_set_privilege(cli, PRIVILEGE_PRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); } else { - /* require password entry */ + // Require password entry cli->state = STATE_ENABLE_PASSWORD; } @@ -605,7 +596,7 @@ struct cli_def *cli_init() { cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Conlfigure from the terminal"); - // and now the built in filters + // And now the built in filters c = cli_register_filter(cli, "begin", cli_range_filter_init, cli_range_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Begin with lines that match"); cli_register_optarg(c, "range_start", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, @@ -618,8 +609,8 @@ struct cli_def *cli_init() { cli_register_optarg(c, "range_end", CLI_CMD_ARGUMENT | CLI_CMD_REMAINDER_OF_LINE, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Stop showing lines that match", NULL, NULL, NULL); - c = cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, - "Count of lines"); + cli_register_filter(cli, "count", cli_count_filter_init, cli_count_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + "Count of lines"); c = cli_register_filter(cli, "exclude", cli_match_filter_init, cli_match_filter, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exclude lines that match"); @@ -665,7 +656,7 @@ void cli_unregister_tree(struct cli_def *cli, struct cli_command *command, int c for (c = command; c;) { p = c->next; - if ((c->command_type == command_type) || (command_type == CLI_ANY_COMMAND)) { + if (c->command_type == command_type || command_type == CLI_ANY_COMMAND) { if (c == cli->commands) cli->commands = c->next; // Unregister all child commands cli_free_command(cli, c); @@ -754,7 +745,7 @@ static int cli_parse_line(const char *line, char *words[], int max_words) { if (!*p) break; - if (inquote) p++; /* skip over trailing quote */ + if (inquote) p++; // Skip over trailing quote inquote = 0; word_start = 0; @@ -803,8 +794,7 @@ int cli_run_command(struct cli_def *cli, const char *command) { int rc = CLI_ERROR; struct cli_pipeline *pipeline; - // split command into pipeline stages, - + // Split command into pipeline stages pipeline = cli_int_generate_pipeline(cli, command); // cli_int_validate_pipeline will deal with buildmode command setup, and return CLI_BUILDMODE_START if found. @@ -830,7 +820,7 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar stage = &pipeline->stage[pipeline->num_stages - 1]; - // check to see if either *no* input, or if the lastchar is a tab. + // Check to see if either *no* input, or if the lastchar is a tab. if ((!stage->words[0] || (command[strlen(command) - 1] == ' ')) && (stage->words[stage->num_words - 1])) stage->num_words++; @@ -847,12 +837,10 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar if (c->command_type != command_type) continue; if (cli->privilege < c->privilege) continue; - if (c->mode != cli->mode && c->mode != MODE_ANY) continue; - if (stage->words[i] && strncasecmp(c->command, stage->words[i], strlen(stage->words[i]))) continue; - // special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set + // Special case for 'buildmode' - skip if the argument for this command was seen, unless MULTIPLE flag is set if (cli->buildmode) { struct cli_optarg *optarg; for (optarg = cli->buildmode->command->optargs; optarg; optarg = optarg->next) { @@ -866,16 +854,14 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar n = c->children; - // if we have no more children, we've matched the *command* - remember this - if (!c->children) { - break; - } + // If we have no more children, we've matched the *command* - remember this + if (!c->children) break; i++; continue; } - if ((lastchar == '?')) { + if (lastchar == '?') { if (asprintf(&strptr, " %-20s %s", c->command, (c->help) ? c->help : "") != -1) { cli_add_comphelp_entry(comphelp, strptr); free_z(strptr); @@ -887,13 +873,13 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar out: if (c) { - // advance past first word of stage + // Advance past first word of stage i++; stage->first_unmatched = i; if (c->optargs) { cli_int_parse_optargs(cli, stage, c, lastchar, comphelp); } else if (lastchar == '?') { - // special case for getting help with no defined optargs.... + // Special case for getting help with no defined optargs.... comphelp->num_entries = -1; } } @@ -902,24 +888,21 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) { - // use cmd as our buffer, and overwrite contents as needed + // Use cmd as our buffer, and overwrite contents as needed. // Backspace to beginning memset((char *)cmd, '\b', cursor); _write(sockfd, cmd, cursor); - // overwrite existing cmd with spaces + // Overwrite existing cmd with spaces memset((char *)cmd, ' ', l); _write(sockfd, cmd, l); - // and backspace again to beginning + // ..and backspace again to beginning memset((char *)cmd, '\b', l); _write(sockfd, cmd, l); - // null cmd buffer + // Null cmd buffer memset((char *)cmd, 0, l); - - // reset pointers to beginning - cursor = l = 0; } void cli_reprompt(struct cli_def *cli) { @@ -938,7 +921,7 @@ void cli_regular_interval(struct cli_def *cli, int seconds) { cli->timeout_tm.tv_usec = 0; } -#define DES_PREFIX "{crypt}" /* to distinguish clear text from DES crypted */ +#define DES_PREFIX "{crypt}" // To distinguish clear text from DES crypted #define MD5_PREFIX "$1$" static int pass_matches(const char *pass, const char *attempt) { @@ -946,9 +929,7 @@ static int pass_matches(const char *pass, const char *attempt) { if ((des = !strncasecmp(pass, DES_PREFIX, sizeof(DES_PREFIX) - 1))) pass += sizeof(DES_PREFIX) - 1; #ifndef WIN32 - /* - * TODO - find a small crypt(3) function for use on windows - */ + // TODO(dparrish): Find a small crypt(3) function for use on windows if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX) - 1)) attempt = crypt(attempt, pass); #endif @@ -1012,11 +993,11 @@ int cli_loop(struct cli_def *cli, int sockfd) { // Set the last action now so we don't time immediately if (cli->idle_timeout) time(&cli->last_action); - /* start off in unprivileged mode */ + // Start off in unprivileged mode cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); - /* no auth required? */ + // No auth required? if (!cli->users && !cli->auth_callback) cli->state = STATE_NORMAL; while (1) { @@ -1046,8 +1027,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { fd_set r; /* - * ensure our transient mode is reset to the starting mode on *each* loop traversal - * transient mode is valid only while a command is being evaluated/executed + * Ensure our transient mode is reset to the starting mode on *each* loop traversal transient mode is valid only + * while a command is being evaluated/executed. */ cli->transient_mode = cli->mode; @@ -1085,16 +1066,14 @@ int cli_loop(struct cli_def *cli, int sockfd) { FD_SET(sockfd, &r); if ((sr = select(sockfd + 1, &r, NULL, NULL, &tm)) < 0) { - /* select error */ if (errno == EINTR) continue; - perror("select"); l = -1; break; } if (sr == 0) { - /* timeout every second */ + // Timeout every second if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK) { l = -1; break; @@ -1159,24 +1138,24 @@ int cli_loop(struct cli_def *cli, int sockfd) { is_telnet_option = 0; } - /* handle ANSI arrows */ + // Handle ANSI arrows if (esc) { if (esc == '[') { - /* remap to readline control codes */ + // Remap to readline control codes switch (c) { - case 'A': /* Up */ + case 'A': // Up c = CTRL('P'); break; - case 'B': /* Down */ + case 'B': // Down c = CTRL('N'); break; - case 'C': /* Right */ + case 'C': // Right c = CTRL('F'); break; - case 'D': /* Left */ + case 'D': // Left c = CTRL('B'); break; @@ -1209,12 +1188,12 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* back word, backspace/delete */ + // Back word, backspace/delete if (c == CTRL('W') || c == CTRL('H') || c == 0x7f) { int back = 0; - if (c == CTRL('W')) /* word */ - { + if (c == CTRL('W')) { + // Word int nc = cursor; if (l == 0 || cursor == 0) continue; @@ -1228,8 +1207,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { nc--; back++; } - } else /* char */ - { + } else { + // Char if (l == 0 || cursor == 0) { _write(sockfd, "\a", 1); continue; @@ -1246,18 +1225,18 @@ int cli_loop(struct cli_def *cli, int sockfd) { } else { int i; if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { - // back up one space, then write current buffer followed by a space + // Back up one space, then write current buffer followed by a space _write(sockfd, "\b", 1); _write(sockfd, cmd + cursor, l - cursor); _write(sockfd, " ", 1); - // move everything one char left + // Move everything one char left memmove(cmd + cursor - 1, cmd + cursor, l - cursor); - // set former last char to null + // Set former last char to null cmd[l - 1] = 0; - // and reposition cursor + // And reposition cursor for (i = l; i >= cursor; i--) _write(sockfd, "\b", 1); } cursor--; @@ -1269,7 +1248,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { } } - /* redraw */ + // Redraw if (c == CTRL('L')) { int i; int cursorback = l - cursor; @@ -1285,7 +1264,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* clear line */ + // Clear line if (c == CTRL('U')) { if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) memset(cmd, 0, l); @@ -1296,7 +1275,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* kill to EOL */ + // Kill to EOL if (c == CTRL('K')) { if (cursor == l) continue; @@ -1312,7 +1291,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* EOT */ + // EOT if (c == CTRL('D')) { if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) break; @@ -1322,7 +1301,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { break; } - /* disable */ + // Disable if (c == CTRL('Z')) { if (cli->mode != MODE_EXEC) { if (cli->buildmode) cli_int_free_buildmode(cli); @@ -1335,19 +1314,18 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* TAB completion */ + // TAB completion if (c == CTRL('I')) { struct cli_comphelp comphelp = {0}; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; - if (cursor != l) continue; cli_get_completions(cli, cmd, c, &comphelp); if (comphelp.num_entries == 0) { _write(sockfd, "\a", 1); } else if (lastchar == CTRL('I')) { - // double tab + // Double tab int i; for (i = 0; i < comphelp.num_entries; i++) { if (i % 4 == 0) @@ -1360,7 +1338,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { cli->showprompt = 1; } else if (comphelp.num_entries == 1) { // Single completion - show *unless* the optional/required 'prefix' is present - if ((comphelp.entries[0][0] != '[') && (comphelp.entries[0][0] != '<')) { + if (comphelp.entries[0][0] != '[' && comphelp.entries[0][0] != '<') { for (; l > 0; l--, cursor--) { if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); @@ -1371,29 +1349,29 @@ int cli_loop(struct cli_def *cli, int sockfd) { cursor = l; _write(sockfd, comphelp.entries[0], strlen(comphelp.entries[0])); _write(sockfd, " ", 1); - // and now forget the tab, since we just found a single match + // And now forget the tab, since we just found a single match lastchar = '\0'; } else { // Yes, we had a match, but it wasn't required - remember the tab in case the user double tabs.... lastchar = CTRL('I'); } } else if (comphelp.num_entries > 1) { - /* More than one completion - * Show as many characters as we can until the completions start to differ + /* + * More than one completion. + * Show as many characters as we can until the completions start to differ. */ lastchar = c; int i, j, k = 0; char *tptr = comphelp.entries[0]; - /* quickly try to see where our entries differ - * corner cases - * - if all entries are optional, don't show - * *any* options unless user has provided a letter. - * - if any entry starts with '<' then don't fill in - * anything. + /* + * Quickly try to see where our entries differ. + * Corner cases: + * - If all entries are optional, don't show *any* options unless user has provided a letter. + * - If any entry starts with '<' then don't fill in anything. */ - // skip a leading '[' + // Skip a leading '[' k = strlen(tptr); if (*tptr == '[') tptr++; @@ -1414,12 +1392,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { k = j; } - /* - * ok, try to show minimum match string if we have a - * non-zero k and the first letter of the last word - * is not '[' - */ - if (k && (comphelp.entries[comphelp.num_entries - 1][0] != '[')) { + // Try to show minimum match string if we have a non-zero k and the first letter of the last word is not '['. + if (k && comphelp.entries[comphelp.num_entries - 1][0] != '[') { for (; l > 0; l--, cursor--) { if (cmd[l - 1] == ' ' || cmd[l - 1] == '|' || (comphelp.comma_separated && cmd[l - 1] == ',')) break; _write(sockfd, "\b", 1); @@ -1437,14 +1411,13 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* '?' at end of line - generate applicable 'help' messages */ - if ((c == '?') && (cursor == l)) { + // '?' at end of line - generate applicable 'help' messages + if (c == '?' && cursor == l) { struct cli_comphelp comphelp = {0}; int i; int show_cr = 1; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; - if (cursor != l) continue; cli_get_completions(cli, cmd, c, &comphelp); @@ -1465,14 +1438,14 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (comphelp.num_entries >= 0) continue; } - /* history */ + // History if (c == CTRL('P') || c == CTRL('N')) { int history_found = 0; if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) continue; - if (c == CTRL('P')) // Up - { + if (c == CTRL('P')) { + // Up in_history--; if (in_history < 0) { for (in_history = MAX_HISTORY - 1; in_history >= 0; in_history--) { @@ -1484,8 +1457,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { } else { if (cli->history[in_history]) history_found = 1; } - } else // Down - { + } else { + // Down in_history++; if (in_history >= MAX_HISTORY || !cli->history[in_history]) { int i = 0; @@ -1512,17 +1485,17 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* left/right cursor motion */ + // Left/right cursor motion if (c == CTRL('B') || c == CTRL('F')) { - if (c == CTRL('B')) /* Left */ - { + if (c == CTRL('B')) { + // Left if (cursor) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\b", 1); cursor--; } - } else /* Right */ - { + } else { + // Right if (cursor < l) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, &cmd[cursor], 1); @@ -1533,8 +1506,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* start of line */ if (c == CTRL('A')) { + // Start of line if (cursor) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) { _write(sockfd, "\r", 1); @@ -1547,8 +1520,8 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* end of line */ if (c == CTRL('E')) { + // End of line if (cursor < l) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, &cmd[cursor], l - cursor); @@ -1559,15 +1532,15 @@ int cli_loop(struct cli_def *cli, int sockfd) { continue; } - /* normal character typed */ if (cursor == l) { - /* append to end of line if not at end-of-buffer */ + // Normal character typed. + // Append to end of line if not at end-of-buffer. if (l < CLI_MAX_LINE_LENGTH - 1) { cmd[cursor] = c; l++; cursor++; } else { - // end-of-buffer, ensure null terminated + // End-of-buffer, ensure null terminated cmd[cursor] = 0; _write(sockfd, "\a", 1); continue; @@ -1581,8 +1554,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { // Insert new character cmd[cursor] = c; - // IMPORTANT - if at end of buffer, set last char to NULL and don't - // change length otherwise bump length by 1 + // IMPORTANT - if at end of buffer, set last char to NULL and don't change length, otherwise bump length by 1 if (l == CLI_MAX_LINE_LENGTH - 1) { cmd[l] = 0; } else { @@ -1616,13 +1588,13 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (cli->state == STATE_LOGIN) { if (l == 0) continue; - /* require login */ + // Require login free_z(username); if (!(username = strdup(cmd))) return 0; cli->state = STATE_PASSWORD; cli->showprompt = 1; } else if (cli->state == STATE_PASSWORD) { - /* require password */ + // Require password int allowed = 0; free_z(password); @@ -1655,12 +1627,12 @@ int cli_loop(struct cli_def *cli, int sockfd) { } else if (cli->state == STATE_ENABLE_PASSWORD) { int allowed = 0; if (cli->enable_password) { - /* check stored static enable password */ + // Check stored static enable password if (pass_matches(cli->enable_password, cmd)) allowed++; } if (!allowed && cli->enable_callback) { - /* check callback */ + // Check callback if (cli->enable_callback(cmd)) allowed++; } @@ -1679,18 +1651,18 @@ int cli_loop(struct cli_def *cli, int sockfd) { rc = cli_run_command(cli, cmd); switch (rc) { case CLI_BUILDMODE_ERROR: - // unable to enter buildmode successfully + // Unable to enter buildmode successfully cli_print(cli, "Failure entering build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); continue; case CLI_BUILDMODE_CANCEL: - // called if user enters 'cancel' + // Called if user enters 'cancel' cli_print(cli, "Canceling build mode for '%s'", cli->buildmode->cname); cli_int_free_buildmode(cli); break; case CLI_BUILDMODE_EXIT: - // called when user enters exit - rebuild *entire* command line - // recall all located optargs + // Called when user enters exit - rebuild *entire* command line. + // Recall all located optargs cli->found_optargs = cli->buildmode->found_optargs; rc = cli_int_execute_buildmode(cli); case CLI_QUIT: @@ -1705,8 +1677,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { if (rc == CLI_QUIT) break; } - // Update the last_action time now as the last command run could take a - // long time to return + // Update the last_action time now as the last command run could take a long time to return if (cli->idle_timeout) time(&cli->last_action); } @@ -1730,7 +1701,8 @@ int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode) { char *cmd; char *end; - if (fgets(buf, CLI_MAX_LINE_LENGTH - 1, fh) == NULL) break; /* end of file */ + // End of file + if (fgets(buf, CLI_MAX_LINE_LENGTH - 1, fh) == NULL) break; if ((p = strpbrk(buf, "#\r\n"))) *p = 0; @@ -1749,7 +1721,7 @@ int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode) { } cli_set_privilege(cli, oldpriv); - cli_set_configmode(cli, oldmode, NULL /* didn't save desc */); + cli_set_configmode(cli, oldmode, NULL); return CLI_OK; } @@ -1758,7 +1730,7 @@ static void _print(struct cli_def *cli, int print_mode, const char *format, va_l int n; char *p = NULL; - if (!cli) return; // sanity check + if (!cli) return; n = vasprintf(&p, format, ap); if (n < 0) return; @@ -1834,7 +1806,7 @@ struct cli_match_filter_state { }; int cli_search_flags_validator(struct cli_def *cli, const char *word, const char *value) { - // valid search flags starts with a hyphen, then any number of i,v, or e characters + // Valid search flags starts with a hyphen, then any number of i, v, or e characters. if ((*value++ == '-') && (*value) && (strspn(value, "vie") == strlen(value))) return CLI_OK; return CLI_ERROR; @@ -1875,7 +1847,8 @@ int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli rflags |= REG_ICASE; break; - case 'e': // implies next term is search string, so stop processing flags + case 'e': + // Implies next term is search string, so stop processing flags break; } } @@ -1887,9 +1860,7 @@ int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli } #else } else { - /* - * No regex functions in windows, so return an error - */ + // No regex functions in windows, so return an error. return CLI_ERROR; } #endif @@ -1901,8 +1872,7 @@ int cli_match_filter(UNUSED(struct cli_def *cli), const char *string, void *data struct cli_match_filter_state *state = data; int r = CLI_ERROR; - if (!string) // clean up - { + if (!string) { if (state->flags & MATCH_REGEX) regfree(&state->match.re); else @@ -1939,8 +1909,7 @@ int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli char *from = strdup(cli_get_optarg_value(cli, "range_start", NULL)); char *to = strdup(cli_get_optarg_value(cli, "range_end", NULL)); - // Do not have to check from/to since we would not have gotten here if we were - // missing a required argument + // Do not have to check from/to since we would not have gotten here if we were missing a required argument. filt->filter = cli_range_filter; filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1); if (state) { @@ -1958,8 +1927,7 @@ int cli_range_filter(UNUSED(struct cli_def *cli), const char *string, void *data struct cli_range_filter_state *state = data; int r = CLI_ERROR; - if (!string) // clean up - { + if (!string) { free_z(state->from); free_z(state->to); free_z(state); @@ -1992,9 +1960,8 @@ int cli_count_filter_init(struct cli_def *cli, int argc, UNUSED(char **argv), st int cli_count_filter(struct cli_def *cli, const char *string, void *data) { int *count = data; - if (!string) // clean up - { - // print count + if (!string) { + // Print count if (cli->client) fprintf(cli->client, "%d\r\n", *count); free(count); @@ -2003,9 +1970,10 @@ int cli_count_filter(struct cli_def *cli, const char *string, void *data) { while (isspace(*string)) string++; - if (*string) (*count)++; // only count non-blank lines + // Only count non-blank lines + if (*string) (*count)++; - return CLI_ERROR; // no output + return CLI_ERROR; } void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *)) { @@ -2060,7 +2028,7 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command return NULL; } - // filters are all registered at the top level + // Filters are all registered at the top level. cli_register_command_core(cli, NULL, c); return c; } @@ -2090,7 +2058,7 @@ char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after) { for (optarg_pair = cli->found_optargs; optarg_pair && !value; optarg_pair = optarg_pair->next) { if (strcmp(optarg_pair->name, name) == 0) { - if (find_after && (find_after == optarg_pair->value)) { + if (find_after && find_after == optarg_pair->value) { find_after = NULL; continue; } @@ -2133,9 +2101,9 @@ int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, in struct cli_optarg *ptr = NULL; int retval = CLI_ERROR; - // name must not already exist with this priv/mode + // Name must not already exist with this priv/mode for (ptr = cmd->optargs, lastopt = NULL; ptr; lastopt = ptr, ptr = ptr->next) { - if (!(strcmp(name, ptr->name)) && (ptr->mode == mode) && (ptr->privilege == privilege)) { + if (!strcmp(name, ptr->name) && ptr->mode == mode && ptr->privilege == privilege) { return CLI_ERROR; } } @@ -2168,18 +2136,18 @@ int cli_unregister_optarg(struct cli_command *cmd, const char *name) { struct cli_optarg *ptr; struct cli_optarg *lastptr; int retval = CLI_ERROR; - // iterate looking for this option name, stopping at end or if name matches + // Iterate looking for this option name, stopping at end or if name matches for (lastptr = NULL, ptr = cmd->optargs; ptr && strcmp(ptr->name, name); lastptr = ptr, ptr = ptr->next) ; - // if ptr, then we found the optarg to delete + // If ptr, then we found the optarg to delete if (ptr) { if (lastptr) { - // not first optarg + // Not first optarg lastptr->next = ptr->next; ptr->next = NULL; } else { - // first optarg + // First optarg cmd->optargs = ptr->next; ptr->next = NULL; } @@ -2221,22 +2189,22 @@ int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char * for (optarg_pair = cli->found_optargs, anchor = &cli->found_optargs; optarg_pair; anchor = &optarg_pair->next, optarg_pair = optarg_pair->next) { - // break if we found this name *and* allow_multiple is false + // Break if we found this name *and* allow_multiple is false if (!strcmp(optarg_pair->name, name) && !allow_multiple) { break; } } - // if we *didn't* find this, then allocate a new entry before proceeding + // If we *didn't* find this, then allocate a new entry before proceeding if (!optarg_pair) { optarg_pair = (struct cli_optarg_pair *)calloc(1, sizeof(struct cli_optarg_pair)); *anchor = optarg_pair; } - // set the value + // Set the value if (optarg_pair) { - // name is null only if we didn't find it + // Name is null only if we didn't find it if (!optarg_pair->name) optarg_pair->name = strdup(name); - // value may be overwritten, so free any old value. + // Value may be overwritten, so free any old value. if (optarg_pair->value) free_z(optarg_pair->value); optarg_pair->value = strdup(value); @@ -2255,11 +2223,11 @@ char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_aft struct cli_optarg_pair *optarg_pair; for (optarg_pair = cli->found_optargs; !value && optarg_pair; optarg_pair = optarg_pair->next) { - // check next entry if this isn't our name + // Check next entry if this isn't our name if (strcasecmp(optarg_pair->name, name)) continue; - // did we have a find_after, then ignore anything up until our find_after match - if ((find_after) && (optarg_pair->value == find_after)) { + // Did we have a find_after, then ignore anything up until our find_after match + if (find_after && optarg_pair->value == find_after) { find_after = NULL; continue; } else if (!find_after) { @@ -2291,25 +2259,23 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag goto out; } - // clean up any shrapnel from earlier - shouldn't be any but.... - if (cli->buildmode) { - cli_int_free_buildmode(cli); - } + // Clean up any shrapnel from earlier - shouldn't be any but.... + if (cli->buildmode) cli_int_free_buildmode(cli); - // assign it so cli_int_register_buildmode_command() has something to work with + // Assign it so cli_int_register_buildmode_command() has something to work with cli->buildmode = buildmode; cli->buildmode->mode = cli->mode; cli->buildmode->transient_mode = cli->transient_mode; if (mode_text) cli->buildmode->mode_text = strdup(mode_text); - // need this to verify we have all *required* arguments + // Need this to verify we have all *required* arguments cli->buildmode->command = stage->command; - // build new *limited* list of commands from this commands optargs + // Build new *limited* list of commands from this commands optargs for (optarg = stage->command->optargs; optarg; optarg = optarg->next) { - // don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel/show/unset - if (!strcmp(optarg->name, "cancel") || (!strcmp(optarg->name, "exit")) || - !strcmp(optarg->name, "show") || (!strcmp(optarg->name, "unset"))) { + // Don't allow anything that could redefine our mode or buildmode mode, or redefine exit/cancel/show/unset + if (!strcmp(optarg->name, "cancel") || !strcmp(optarg->name, "exit") || !strcmp(optarg->name, "show") || + !strcmp(optarg->name, "unset")) { cli_error(cli, "Default buildmode command conflicts with optarg named %s", optarg->name); rc = CLI_BUILDMODE_ERROR; goto out; @@ -2344,7 +2310,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag } } cli->buildmode->cname = strdup(cli_command_name(cli, stage->command)); - // and lastly four 'always there' commands to cancel current mode and to execute the command, show settings, and unset + // And lastly four 'always there' commands to cancel current mode and to execute the command, show settings, and unset cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); cli_int_register_buildmode_command(cli, NULL, "exit", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, @@ -2357,9 +2323,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag "setting to clear", cli_int_buildmode_unset_completor, cli_int_buildmode_unset_validator, NULL); out: - if (rc != CLI_BUILDMODE_START) { - cli_int_free_buildmode(cli); - } + if (rc != CLI_BUILDMODE_START) cli_int_free_buildmode(cli); return rc; } @@ -2392,7 +2356,7 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru return NULL; } - // buildmode commmands are all registered at the top level + // Buildmode commmands are all registered at the top level cli_register_command_core(cli, NULL, c); return c; } @@ -2401,7 +2365,6 @@ int cli_int_execute_buildmode(struct cli_def *cli) { struct cli_optarg *optarg = NULL; int rc = CLI_OK; char *cmdline; - char *value = NULL; cmdline = strdup(cli_command_name(cli, cli->buildmode->command)); @@ -2454,10 +2417,8 @@ char *cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { size_t wordlen = strlen(word); int add_quotes = 0; - /* - * Allocate enough space to hold the old string, a space, and the new string (including null terminator). - * Also include enough space for a quote around the string if it contains a whitespace character - */ + // Allocate enough space to hold the old string, a space, and the new string (including null terminator). + // Also include enough space for a quote around the string if it contains a whitespace character if ((tptr = (char *)realloc(cmdline, oldlen + 1 + wordlen + 1 + 2))) { strcat(tptr, " "); for (cptr = word; *cptr; cptr++) { @@ -2483,7 +2444,7 @@ int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char * return rc; } -// a 'flag' callback has no optargs, so we need to set it ourself based on *this* command +// A 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXTEND; @@ -2498,7 +2459,7 @@ int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char return rc; } -// a 'flag' callback has no optargs, so we need to set it ourself based on *this* command +// A 'flag' callback has no optargs, so we need to set it ourself based on *this* command int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXTEND; @@ -2538,7 +2499,7 @@ int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char struct cli_optarg_pair *optarg_pair; if (cli && cli->buildmode) { for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { - // only show vars that are also current 'commands' + // Only show vars that are also current 'commands' struct cli_command *c = cli->commands; for (; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; @@ -2553,18 +2514,17 @@ int cli_int_buildmode_show_cback(struct cli_def *cli, const char *command, char } int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { - // iterate over our 'set' variables to see if that variable is also a 'valid' command right now - + // Iterate over our 'set' variables to see if that variable is also a 'valid' command right now struct cli_command *c; - // is this 'optarg' to remove one of the current commands? + // Is this 'optarg' to remove one of the current commands? for (c = cli->commands; c; c = c->next) { if (c->command_type != CLI_BUILDMODE_COMMAND) continue; if (cli->privilege < c->privilege) continue; if ((cli->buildmode->mode != c->mode) && (cli->buildmode->transient_mode != c->mode) && (c->mode != MODE_ANY)) continue; if (strcmp(c->command, argv[0])) continue; - // Ok, go fry anything by this name + // Go fry anything by this name cli_int_unset_optarg_value(cli, argv[0]); break; @@ -2573,7 +2533,7 @@ int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char return CLI_OK; } -/* Generate a list of variables that *have* been set */ +// Generate a list of variables that *have* been set int cli_int_buildmode_unset_completor(struct cli_def *cli, const char *name, const char *word, struct cli_comphelp *comphelp) { return CLI_OK; @@ -2623,7 +2583,6 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma if (cli->privilege < c->privilege) continue; if (strncasecmp(c->command, stage->words[start_word], c->unique_len)) continue; - if (strncasecmp(c->command, stage->words[start_word], strlen(stage->words[start_word]))) continue; AGAIN: @@ -2672,17 +2631,15 @@ static int cli_int_locate_command(struct cli_def *cli, struct cli_command *comma } return rc; } else if (cli->mode > MODE_CONFIG && c->mode == MODE_CONFIG) { - // command matched but from another mode, - // remember it if we fail to find correct command + // Command matched but from another mode, remember it if we fail to find correct command again_config = c; } else if (c->mode == MODE_ANY) { - // command matched but for any mode, - // remember it if we fail to find correct command + // Command matched but for any mode, remember it if we fail to find correct command again_any = c; } } - // drop out of config submode if we have matched command on MODE_CONFIG + // Drop out of config submode if we have matched command on MODE_CONFIG if (again_config) { c = again_config; goto AGAIN; @@ -2708,7 +2665,7 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline cli->found_optargs = NULL; for (i = 0; i < pipeline->num_stages; i++) { - // in 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs + // In 'buildmode' we only have one pipeline, but we need to recall if we had started with any optargs if (cli->buildmode && i == 0) command_type = CLI_BUILDMODE_COMMAND; @@ -2724,8 +2681,7 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline cli->found_optargs = NULL; rc = cli_int_locate_command(cli, cli->commands, command_type, 0, &pipeline->stage[i]); - // and save our found optargs for later use - + // And save our found optargs for later use if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; else @@ -2738,16 +2694,14 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline return rc; } -struct cli_pipeline *cli_int_free_pipeline(struct cli_pipeline *pipeline) { +void cli_int_free_pipeline(struct cli_pipeline *pipeline) { int i; - if (pipeline) { - for (i = 0; i < pipeline->num_stages; i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs); - for (i = 0; i < pipeline->num_words; i++) free_z(pipeline->words[i]); - free_z(pipeline->cmdline); - free_z(pipeline); - pipeline = NULL; - } - return pipeline; + if (!pipeline) return; + for (i = 0; i < pipeline->num_stages; i++) cli_int_free_found_optargs(&pipeline->stage[i].found_optargs); + for (i = 0; i < pipeline->num_words; i++) free_z(pipeline->words[i]); + free_z(pipeline->cmdline); + free_z(pipeline); + pipeline = NULL; } void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { @@ -2777,10 +2731,8 @@ void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) { } } -/* - * Take an array of words and return a pipeline, using '|' to split command into different 'stages'. - * Pipeline is broken down by '|' characters and within each p - */ +// Take an array of words and return a pipeline, using '|' to split command into different 'stages'. +// Pipeline is broken down by '|' characters and within each p. struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char *command) { int i; struct cli_pipeline_stage *stage; @@ -2804,7 +2756,7 @@ struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char * for (i = 0; i < pipeline->num_words; i++, word++) { if (*word[0] == '|') { if (cli->buildmode) { - // can't allow filters in buildmode commands + // Can't allow filters in buildmode commands cli_int_free_pipeline(pipeline); return NULL; } @@ -2812,7 +2764,7 @@ struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char * stage++; stage->num_words = 0; pipeline->num_stages++; - stage->words = word + 1; // first word of the next stage is one past where we are (possibly NULL) + stage->words = word + 1; // First word of the next stage is one past where we are (possibly NULL) } else { stage->num_words++; } @@ -2891,7 +2843,7 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL; char *tptr = NULL; - // if we've already seen a value by this exact name, skip it, unless the multiple flag is set + // If we've already seen a value by this exact name, skip it, unless the multiple flag is set if (cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) return; get_completions = optarg->get_completions; @@ -2899,7 +2851,7 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { delim_start = DELIM_OPT_START; delim_end = DELIM_OPT_END; - get_completions = NULL; // no point, completor of field is the name itself + get_completions = NULL; // No point, completor of field is the name itself } } else if (optarg->flags & CLI_CMD_HYPHENATED_OPTION) { delim_start = DELIM_OPT_START; @@ -2909,22 +2861,22 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta delim_end = DELIM_ARG_END; } else if (optarg->flags & CLI_CMD_OPTIONAL_ARGUMENT) { /* - * optional args can match against the name the value. + * Optional args can match against the name the value. * Here 'anchor_word' is the name, and 'next_word' is what we're matching against. - * So if anchor_word==next_word we're looking at the 'name' of the optarg, otherwise we - * know the name and are going against the value. + * So if anchor_word==next_word we're looking at the 'name' of the optarg, otherwise we know the name and are going + * against the value. */ if (anchor_word != next_word) { - // matching against optional argument 'value' + // Matching against optional argument 'value' help_insert = 0; if (!get_completions) { delim_start = DELIM_ARG_START; delim_end = DELIM_ARG_END; } } else { - // matching against optional argument 'name' + // Matching against optional argument 'name' help_insert = 1; - get_completions = NULL; // matching against the name, not the following field value + get_completions = NULL; // Matching against the name, not the following field value if (!(anchor_word && !strncmp(anchor_word, optarg->name, strlen(anchor_word)))) { delim_start = DELIM_OPT_START; delim_end = DELIM_OPT_END; @@ -2933,7 +2885,7 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta } // Fill in with help text or completor value(s) as indicated - if ((lastchar == '?') && (asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1)) { + if (lastchar == '?' && asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1) { if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) allow_buildmode = BUILDMODE_YES; if (help_insert && (asprintf(&tptr, " %-20s enter '%s' to %s%s", tptr, optarg->name, (optarg->help) ? optarg->help : "", allow_buildmode) != -1)) { @@ -2957,37 +2909,36 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage *stage, struct cli_command *cmd, char lastchar, struct cli_comphelp *comphelp) { struct cli_optarg *optarg = NULL, *oaptr = NULL; - int w_idx; // word_index - int w_incr; // word_increment + int word_idx, word_incr, candidate_idx; struct cli_optarg *candidates[CLI_MAX_LINE_WORDS]; - int num_candidates = 0; - int c_idx; // candidate_idx char *value; + int num_candidates = 0; int is_last_word = 0; int (*validator)(struct cli_def *, const char *name, const char *value); /* - * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to - * know what has already been found. There should be no ambiguities before the 'last' word. + * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to know what + * has already been found. There should be no ambiguities before the 'last' word. * Note specifically that for tab completions and help the *last* word can be a null pointer. */ stage->error_word = NULL; - /* start our optarg and word pointers at the beginning - * optarg will be incremented *only* when an argument is identified - * w_idx will be incremented either by 1 (optflag or argument) or 2 (optional argument) + /* Start our optarg and word pointers at the beginning. + * optarg will be incremented *only* when an argument is identified. + * word_idx will be incremented either by 1 (optflag or argument) or 2 (optional argument). */ - w_idx = stage->first_unmatched; + word_idx = stage->first_unmatched; optarg = cmd->optargs; num_candidates = 0; - while (optarg && w_idx < stage->num_words && (num_candidates <= 1)) { + while (optarg && word_idx < stage->num_words && num_candidates <= 1) { num_candidates = 0; - w_incr = 1; // assume we're only incrementing by a word - if we match an optional argument bump to 2 + word_incr = 1; // Assume we're only incrementing by a word - if we match an optional argument bump to 2 - /* the initial loop here is to identify candidates based matching *this* word in order against: - * an exact match of the word to the optinal flag/argument name (yield exactly one match and exit the loop) - * a partial match for optional flag/argument name - * candidate an argument. + /* + * The initial loop here is to identify candidates based matching *this* word in order against: + * - An exact match of the word to the optinal flag/argument name (yield exactly one match and exit the loop) + * - A partial match for optional flag/argument name + * - Candidate an argument. */ for (oaptr = optarg; oaptr; oaptr = oaptr->next) { @@ -2995,30 +2946,34 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (cli->privilege < oaptr->privilege) continue; if ((oaptr->mode != cli->mode) && (oaptr->mode != cli->transient_mode) && (oaptr->mode != MODE_ANY)) continue; - /* Two special cases - a hphenated option and an 'exact' match optional flag or optional argument - * If our word starts with a '-' and we have a CMD_CLI_HYPHENATED_OPTION or an exact match for an - * optional flag/argument name trumps anything and will be the *only* candidate - * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a candidate - * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are required + /* + * Two special cases - a hphenated option and an 'exact' match optional flag or optional argument. + * If our word starts with a '-' and we have a CMD_CLI_HYPHENATED_OPTION or an exact match for an optional + * flag/argument name trumps anything and will be the *only* candidate. + * Otherwise if the word is 'blank', could be an argument, or matches 'enough' of an option/flag it is a + * candidate. + * Once we accept an argument as a candidate, we're done looking for candidates as straight arguments are + * required. */ - if ((oaptr->flags & CLI_CMD_SPOT_CHECK) && (num_candidates == 0)) { + if (oaptr->flags & CLI_CMD_SPOT_CHECK && num_candidates == 0) { stage->status = (*oaptr->validator)(cli, NULL, NULL); if (stage->status != CLI_OK) { - stage->error_word = stage->words[w_idx]; + stage->error_word = stage->words[word_idx]; cli_reprompt(cli); return; } - } else if (stage->words[w_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) && - !strcmp(oaptr->name, stage->words[w_idx])) { + } else if (stage->words[word_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) && + !strcmp(oaptr->name, stage->words[word_idx])) { candidates[0] = oaptr; num_candidates = 1; break; - } else if (stage->words[w_idx] && stage->words[w_idx][0] == '-' && (oaptr->flags & (CLI_CMD_HYPHENATED_OPTION))) { + } else if (stage->words[word_idx] && stage->words[word_idx][0] == '-' && + (oaptr->flags & (CLI_CMD_HYPHENATED_OPTION))) { candidates[0] = oaptr; num_candidates = 1; break; - } else if (!stage->words[w_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || - !strncasecmp(oaptr->name, stage->words[w_idx], strlen(stage->words[w_idx]))) { + } else if (!stage->words[word_idx] || (oaptr->flags & CLI_CMD_ARGUMENT) || + !strncasecmp(oaptr->name, stage->words[word_idx], strlen(stage->words[word_idx]))) { candidates[num_candidates++] = oaptr; } if (oaptr->flags & CLI_CMD_ARGUMENT) { @@ -3033,8 +2988,8 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage * - If we have more than one candidate and we're not at end-of-line ( */ if (num_candidates == 0) break; - if ((num_candidates > 1) && ((lastchar == '\0') || (w_idx < (stage->num_words - 1)))) { - stage->error_word = stage->words[w_idx]; + if (num_candidates > 1 && (lastchar == '\0' || word_idx < (stage->num_words - 1))) { + stage->error_word = stage->words[word_idx]; stage->status = CLI_AMBIGUOUS; cli_error(cli, "Ambiguous option/argument for command %s", stage->command->command); return; @@ -3042,73 +2997,73 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage /* * So now we could have one or more candidates. We need to call get help/completions *only* if this is the - * 'last-word' + * 'last-word'. * Remember that last word for optional arguments is last or next to last.... */ if (lastchar != '\0') { int called_comphelp = 0; - for (c_idx = 0; c_idx < num_candidates; c_idx++) { - oaptr = candidates[c_idx]; - - // need to know *which* word we're trying to complete for optional_args, hence the difference calls - if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (w_idx == (stage->num_words - 1))) || - ((oaptr->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_HYPHENATED_OPTION)) && - (w_idx == (stage->num_words - 1)))) { - cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], - stage->words[w_idx]); + for (candidate_idx = 0; candidate_idx < num_candidates; candidate_idx++) { + oaptr = candidates[candidate_idx]; + + // Need to know *which* word we're trying to complete for optional_args, hence the difference calls + if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (word_idx == (stage->num_words - 1))) || + (oaptr->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_HYPHENATED_OPTION) && + word_idx == (stage->num_words - 1))) { + cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[word_idx], + stage->words[word_idx]); called_comphelp = 1; - } else if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 2))) { - cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[w_idx], - stage->words[w_idx + 1]); + } else if (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT && word_idx == (stage->num_words - 2)) { + cli_get_optarg_comphelp(cli, oaptr, comphelp, num_candidates, lastchar, stage->words[word_idx], + stage->words[word_idx + 1]); called_comphelp = 1; } } - // if we were 'end-of-word' and looked for completions/help, return to user + // If we were 'end-of-word' and looked for completions/help, return to user if (called_comphelp) { stage->status = CLI_OK; return; } } - // set some values for use later - makes code much easier to read - value = stage->words[w_idx]; + // Set some values for use later - makes code much easier to read + value = stage->words[word_idx]; oaptr = candidates[0]; validator = oaptr->validator; - if (((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT)) && (w_idx == (stage->num_words - 1))) || - ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) && (w_idx == (stage->num_words - 2)))) { + if ((oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_ARGUMENT) && word_idx == (stage->num_words - 1)) || + (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT && word_idx == (stage->num_words - 2))) { is_last_word = 1; } - if ((oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT)) { - w_incr = 2; - if (!stage->words[w_idx + 1] && lastchar == '\0') { - // hit a optional argument that does not have a value with it - cli_error(cli, "Optional argument %s requires a value", stage->words[w_idx]); - stage->error_word = stage->words[w_idx]; + if (oaptr->flags & CLI_CMD_OPTIONAL_ARGUMENT) { + word_incr = 2; + if (!stage->words[word_idx + 1] && lastchar == '\0') { + // Hit an optional argument that does not have a value with it + cli_error(cli, "Optional argument %s requires a value", stage->words[word_idx]); + stage->error_word = stage->words[word_idx]; stage->status = CLI_MISSING_VALUE; return; } - value = stage->words[w_idx + 1]; + value = stage->words[word_idx + 1]; } /* - * Ok, so we're not at end of string and doing help/completions. - * So see if our value is 'valid', to save it, and see if we have any extra processing to - * do such as a transient mode check or enter build mode. + * We're not at end of string and doing help/completions. + * So see if our value is 'valid', to save it, and see if we have any extra processing to do such as a transient + * mode check or enter build mode. */ - if (!validator || ((*validator)(cli, oaptr->name, value) == CLI_OK)) { + if (!validator || (*validator)(cli, oaptr->name, value) == CLI_OK) { if (oaptr->flags & CLI_CMD_DO_NOT_RECORD) { - // we want completion and validation, but then leave this 'value' to be seen - used *only* by buildmode - // as argv[0] with argc=1 + // We want completion and validation, but then leave this 'value' to be seen - used *only* by buildmode as + // argv[0] with argc=1 break; } else { - // need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing + // Need to combine remaining words if the CLI_CMD_REMAINDER_OF_LINE flag it set, then we're done processing int set_value_return = 0; if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { char *combined = NULL; - combined = join_words(stage->num_words - w_idx, stage->words + w_idx); + combined = join_words(stage->num_words - word_idx, stage->words + word_idx); set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); free_z(combined); } else { @@ -3117,49 +3072,47 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (set_value_return != CLI_OK) { cli_error(cli, "%sProblem setting value for command argument %s", lastchar == '\0' ? "" : "\n", - stage->words[w_idx]); + stage->words[word_idx]); cli_reprompt(cli); - stage->error_word = stage->words[w_idx]; + stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; return; } } } else { cli_error(cli, "%sProblem parsing command setting %s with value %s", lastchar == '\0' ? "" : "\n", oaptr->name, - stage->words[w_idx]); + stage->words[word_idx]); cli_reprompt(cli); - stage->error_word = stage->words[w_idx]; + stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; return; } - // if this optarg can set the transient mode, then evaluate it if we're not at last word - if (oaptr->transient_mode && (oaptr->transient_mode(cli, oaptr->name, value))) { - stage->error_word = stage->words[w_idx]; + // If this optarg can set the transient mode, then evaluate it if we're not at last word + if (oaptr->transient_mode && oaptr->transient_mode(cli, oaptr->name, value)) { + stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; return; } - // only do buildmode optargs if we're a executing a command, parsing command (stage 0), and this is the last word + // Only do buildmode optargs if we're a executing a command, parsing command (stage 0), and this is the last word if ((stage->status == CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { stage->status = cli_int_enter_buildmode(cli, stage, value); return; } - /* - * Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg - * starting point when we see a true argument - */ + // Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg + // starting point when we see a true argument if (oaptr->flags & CLI_CMD_ARGUMENT) { - // advance pass this argument entry + // Advance past this argument entry optarg = oaptr->next; } - w_idx += w_incr; - stage->first_unmatched = w_idx; + word_idx += word_incr; + stage->first_unmatched = word_idx; } - // Ok, if we're evaluating the command for execution, ensure we have all required arguments. + // If we're evaluating the command for execution, ensure we have all required arguments. if (lastchar == '\0') { for (; optarg; optarg = optarg->next) { if (cli->privilege < optarg->privilege) continue; From f227a4ae3cf3c273a7bb72f6bd5f3b0bae5c645c Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 23 Jul 2019 11:16:13 -0400 Subject: [PATCH 21/25] Started from upstream/Optargs branch - fixed makefile/spec tweaks to inclue doc dir and build rpm --- Makefile | 2 +- libcli.spec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b944720..94a3366 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ install: $(TARGET_LIBS) rpmprep: rm -rf libcli-$(MAJOR).$(MINOR).$(REVISION) mkdir libcli-$(MAJOR).$(MINOR).$(REVISION) - cp -R libcli.{c,h} libcli.spec clitest.c Makefile COPYING README libcli-$(MAJOR).$(MINOR).$(REVISION) + cp -R libcli.{c,h} libcli.spec clitest.c Makefile COPYING README.md doc libcli-$(MAJOR).$(MINOR).$(REVISION) tar zcvf libcli-$(MAJOR).$(MINOR).$(REVISION).tar.gz --exclude CVS --exclude *.tar.gz libcli-$(MAJOR).$(MINOR).$(REVISION) rm -rf libcli-$(MAJOR).$(MINOR).$(REVISION) diff --git a/libcli.spec b/libcli.spec index e653cb6..4ff1b48 100644 --- a/libcli.spec +++ b/libcli.spec @@ -60,14 +60,14 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %files devel -%doc README +%doc README.md doc/developers-guide.md %{_libdir}/*.so* %{_libdir}/*.a %{_includedir}/*.h %defattr(-, root, root) %changelog -* Mon Jul 16 2019 Rob Sanders 1.10.0-1 +* Tue Jul 16 2019 Rob Sanders 1.10.0-1 - Add support for named arguments, optional flags, and optional arguments - Support help and tab complete for options/arguments - Enable users to add custom 'filters', including support for options/arguments From dd44a2690cd9ea3ecb47390b85dc4676191bfa2e Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 23 Jul 2019 16:04:26 -0400 Subject: [PATCH 22/25] Fix memory leak while doing completions --- libcli.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/libcli.c b/libcli.c index d2b388a..6fb9ccb 100644 --- a/libcli.c +++ b/libcli.c @@ -2916,6 +2916,8 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage int is_last_word = 0; int (*validator)(struct cli_def *, const char *name, const char *value); + if (cli->buildmode) cli->found_optargs = cli->buildmode->found_optargs; + else cli->found_optargs = stage->found_optargs; /* * Tab completion and help are *only* allowed at end of string, but we need to process the entire command to know what * has already been found. There should be no ambiguities before the 'last' word. @@ -2960,7 +2962,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (stage->status != CLI_OK) { stage->error_word = stage->words[word_idx]; cli_reprompt(cli); - return; + goto done; } } else if (stage->words[word_idx] && (oaptr->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) && !strcmp(oaptr->name, stage->words[word_idx])) { @@ -2992,7 +2994,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage stage->error_word = stage->words[word_idx]; stage->status = CLI_AMBIGUOUS; cli_error(cli, "Ambiguous option/argument for command %s", stage->command->command); - return; + goto done; } /* @@ -3021,7 +3023,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage // If we were 'end-of-word' and looked for completions/help, return to user if (called_comphelp) { stage->status = CLI_OK; - return; + goto done; } } @@ -3041,7 +3043,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage cli_error(cli, "Optional argument %s requires a value", stage->words[word_idx]); stage->error_word = stage->words[word_idx]; stage->status = CLI_MISSING_VALUE; - return; + goto done; } value = stage->words[word_idx + 1]; } @@ -3076,7 +3078,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage cli_reprompt(cli); stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; - return; + goto done; } } } else { @@ -3085,20 +3087,20 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage cli_reprompt(cli); stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; - return; + goto done; } // If this optarg can set the transient mode, then evaluate it if we're not at last word if (oaptr->transient_mode && oaptr->transient_mode(cli, oaptr->name, value)) { stage->error_word = stage->words[word_idx]; stage->status = CLI_ERROR; - return; + goto done; } // Only do buildmode optargs if we're a executing a command, parsing command (stage 0), and this is the last word if ((stage->status == CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { stage->status = cli_int_enter_buildmode(cli, stage, value); - return; + goto done; } // Optional flags and arguments can appear multiple times, but true arguments only once. Advance our optarg @@ -3121,10 +3123,14 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (optarg->flags & CLI_CMD_ARGUMENT) { cli_error(cli, "Incomplete command, missing required argument '%s'", optarg->name); stage->status = CLI_MISSING_ARGUMENT; - return; + goto done; } } } + +done: + if (cli->buildmode) cli->buildmode->found_optargs = cli->found_optargs; + else stage->found_optargs = cli->found_optargs; return; } From a71ff5e0892cc090a3aa240dc61473899d9c44c0 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 23 Jul 2019 16:15:31 -0400 Subject: [PATCH 23/25] Rename cli_int_add_optarg_value to cli_set_optarg_value and make public --- clitest.c | 2 ++ libcli.c | 11 +++++------ libcli.h | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/clitest.c b/clitest.c index afb52bd..dc9f8b2 100644 --- a/clitest.c +++ b/clitest.c @@ -254,9 +254,11 @@ int shape_transient_eval(struct cli_def *cli, const char *name, const char *valu printf("shape_transient_eval called with <%s>\n", value); if (!strcmp(value, "rectangle")) { cli_set_transient_mode(cli, MODE_POLYGON_RECTANGLE); + cli_set_optarg_value(cli, "duplicateShapeValue", value, 0); return CLI_OK; } else if (!strcmp(value, "triangle")) { cli_set_transient_mode(cli, MODE_POLYGON_TRIANGLE); + cli_set_optarg_value(cli, "duplicateShapeValue", value, 0); return CLI_OK; } cli_error(cli, "unrecognized value for setting %s -> %s", name, value); diff --git a/libcli.c b/libcli.c index 6fb9ccb..b146323 100644 --- a/libcli.c +++ b/libcli.c @@ -141,7 +141,6 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage static int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stage, char *mode_text); static char *cli_int_buildmode_extend_cmdline(char *, char *word); static void cli_int_free_buildmode(struct cli_def *cli); -static int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); static void cli_free_command(struct cli_def *cli, struct cli_command *cmd); static int cli_int_unregister_command_core(struct cli_def *cli, const char *command, int command_type); static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *command) __attribute__((unused)); @@ -2183,7 +2182,7 @@ void cli_int_unset_optarg_value(struct cli_def *cli, const char *name) { } } -int cli_int_add_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple) { +int cli_set_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple) { struct cli_optarg_pair *optarg_pair, **anchor; int rc = CLI_ERROR; @@ -2452,7 +2451,7 @@ int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } - if (cli_int_add_optarg_value(cli, command, command, 0)) { + if (cli_set_optarg_value(cli, command, command, 0)) { cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } @@ -2467,7 +2466,7 @@ int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *comma cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } - if (cli_int_add_optarg_value(cli, command, command, CLI_CMD_OPTION_MULTIPLE)) { + if (cli_set_optarg_value(cli, command, command, CLI_CMD_OPTION_MULTIPLE)) { cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } @@ -3066,10 +3065,10 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage if (oaptr->flags & CLI_CMD_REMAINDER_OF_LINE) { char *combined = NULL; combined = join_words(stage->num_words - word_idx, stage->words + word_idx); - set_value_return = cli_int_add_optarg_value(cli, oaptr->name, combined, 0); + set_value_return = cli_set_optarg_value(cli, oaptr->name, combined, 0); free_z(combined); } else { - set_value_return = cli_int_add_optarg_value(cli, oaptr->name, value, oaptr->flags & CLI_CMD_OPTION_MULTIPLE); + set_value_return = cli_set_optarg_value(cli, oaptr->name, value, oaptr->flags & CLI_CMD_OPTION_MULTIPLE); } if (set_value_return != CLI_OK) { diff --git a/libcli.h b/libcli.h index 73cafa7..3361744 100644 --- a/libcli.h +++ b/libcli.h @@ -241,6 +241,7 @@ char *cli_find_optarg_value(struct cli_def *cli, char *name, char *find_after); struct cli_optarg_pair *cli_get_all_found_optargs(struct cli_def *cli); int cli_unregister_optarg(struct cli_command *cmd, const char *name); char *cli_get_optarg_value(struct cli_def *cli, const char *name, char *find_after); +int cli_set_optarg_value(struct cli_def *cli, const char *name, const char *value, int allow_multiple); void cli_unregister_all_optarg(struct cli_command *c); void cli_unregister_all_filters(struct cli_def *cli); void cli_unregister_all_commands(struct cli_def *cli); From 47cd649d2916615422db16c2c0546b5a0b20db2d Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 23 Jul 2019 16:17:58 -0400 Subject: [PATCH 24/25] Fix memory leak in cli_get_optarg_comphelp when formatting 'help' entries --- libcli.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libcli.c b/libcli.c index b146323..70dd77f 100644 --- a/libcli.c +++ b/libcli.c @@ -2841,7 +2841,8 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta char *allow_buildmode = BUILDMODE_NO; int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL; char *tptr = NULL; - + char *tname = NULL; + // If we've already seen a value by this exact name, skip it, unless the multiple flag is set if (cli_find_optarg_value(cli, optarg->name, NULL) && !(optarg->flags & (CLI_CMD_OPTION_MULTIPLE))) return; @@ -2884,16 +2885,17 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta } // Fill in with help text or completor value(s) as indicated - if (lastchar == '?' && asprintf(&tptr, "%s%s%s", delim_start, optarg->name, delim_end) != -1) { + if (lastchar == '?' && asprintf(&tname, "%s%s%s", delim_start, optarg->name, delim_end) != -1) { if (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) allow_buildmode = BUILDMODE_YES; - if (help_insert && (asprintf(&tptr, " %-20s enter '%s' to %s%s", tptr, optarg->name, + if (help_insert && (asprintf(&tptr, " %-20s enter '%s' to %s%s", tname, optarg->name, (optarg->help) ? optarg->help : "", allow_buildmode) != -1)) { cli_add_comphelp_entry(comphelp, tptr); free_z(tptr); - } else if (asprintf(&tptr, " %-20s %s%s", tptr, (optarg->help) ? optarg->help : "", allow_buildmode) != -1) { + } else if (asprintf(&tptr, " %-20s %s%s", tname, (optarg->help) ? optarg->help : "", allow_buildmode) != -1) { cli_add_comphelp_entry(comphelp, tptr); free_z(tptr); } + free_z(tname); } else if (lastchar == CTRL('I')) { if (get_completions) { (*get_completions)(cli, optarg->name, next_word, comphelp); From 399d055a0de82bc3b3b77d75349d64118a1bbfc9 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 23 Jul 2019 16:32:35 -0400 Subject: [PATCH 25/25] Update changelog in spec file, bump release number to 1.10.0-2 (from 1.10.0-1) --- libcli.spec | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libcli.spec b/libcli.spec index 4ff1b48..88e6de1 100644 --- a/libcli.spec +++ b/libcli.spec @@ -1,7 +1,7 @@ Version: 1.10.0 Summary: Cisco-like telnet command-line library Name: libcli -Release: 1 +Release: 2 License: LGPL Group: Library/Communication Source: %{name}-%{version}.tar.gz @@ -67,6 +67,11 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Tue Jul 23 2019 Rob Sanders 1.10.0-2 +- Fix spec file and rpm build issues +- Fix 2 memory leaks (tab completion and help formatting) +- Expose cli_set_optarg_value() for external use + * Tue Jul 16 2019 Rob Sanders 1.10.0-1 - Add support for named arguments, optional flags, and optional arguments - Support help and tab complete for options/arguments