From 72898001220c0e61e77362d2ca1756da2d57efa0 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 31 Jul 2019 13:38:08 -0400 Subject: [PATCH 01/21] Bump spec to 1.10.1-1, expose help|exit|quit|history|enable|disable commands --- Makefile | 2 +- libcli.c | 57 ++++++++++++++++++++++++++++++++++++++++------------- libcli.h | 12 +++++++++++ libcli.spec | 9 +++++++-- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 94a3366..9c8a985 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PREFIX = /usr/local MAJOR = 1 MINOR = 10 -REVISION = 0 +REVISION = 1 LIB = libcli.so LIB_STATIC = libcli.a diff --git a/libcli.c b/libcli.c index 70dd77f..fae5c94 100644 --- a/libcli.c +++ b/libcli.c @@ -500,7 +500,7 @@ int cli_show_help(struct cli_def *cli, struct cli_command *c) { return CLI_OK; } -int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { +int cli_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { if (cli->privilege == PRIVILEGE_PRIVILEGED) return CLI_OK; if (!cli->enable_password && !cli->enable_callback) { @@ -515,19 +515,19 @@ int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char return CLI_OK; } -int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { +int cli_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); return CLI_OK; } -int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { +int cli_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_error(cli, "\nCommands available:"); cli_show_help(cli, cli->commands); return CLI_OK; } -int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { +int cli_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { int i; cli_error(cli, "\nCommand history:"); @@ -538,14 +538,14 @@ int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(cha return CLI_OK; } -int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { +int cli_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); return CLI_QUIT; } -int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc) { - if (cli->mode == MODE_EXEC) return cli_int_quit(cli, command, argv, argc); +int cli_exit(struct cli_def *cli, const char *command, char *argv[], int argc) { + if (cli->mode == MODE_EXEC) return cli_quit(cli, command, argv, argc); if (cli->mode > MODE_CONFIG) cli_set_configmode(cli, MODE_CONFIG, NULL); @@ -580,15 +580,15 @@ struct cli_def *cli_init() { } cli->telnet_protocol = 1; - cli_register_command(cli, 0, "help", cli_int_help, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show available commands"); - cli_register_command(cli, 0, "quit", cli_int_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect"); - cli_register_command(cli, 0, "logout", cli_int_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect"); - cli_register_command(cli, 0, "exit", cli_int_exit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exit from current mode"); - cli_register_command(cli, 0, "history", cli_int_history, PRIVILEGE_UNPRIVILEGED, MODE_ANY, + cli_register_command(cli, 0, "help", cli_help, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show available commands"); + cli_register_command(cli, 0, "quit", cli_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect"); + cli_register_command(cli, 0, "logout", cli_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect"); + cli_register_command(cli, 0, "exit", cli_exit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exit from current mode"); + cli_register_command(cli, 0, "history", cli_history, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show a list of previously run commands"); - cli_register_command(cli, 0, "enable", cli_int_enable, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, + cli_register_command(cli, 0, "enable", cli_enable, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Turn on privileged commands"); - cli_register_command(cli, 0, "disable", cli_int_disable, PRIVILEGE_PRIVILEGED, MODE_EXEC, + cli_register_command(cli, 0, "disable", cli_disable, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Turn off privileged commands"); c = cli_register_command(cli, 0, "configure", 0, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enter configuration mode"); @@ -3142,3 +3142,32 @@ void cli_unregister_all_commands(struct cli_def *cli) { void cli_unregister_all_filters(struct cli_def *cli) { cli_unregister_tree(cli, cli->commands, CLI_FILTER_COMMAND); } + +/* + * Several routines were declared as internal, but would be useful for external use also + * Rename them so they can be exposed, but have original routines simply call the 'public' ones + */ + +int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { + return cli_quit(cli, command, argv, argc); +} + +int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { + return cli_help(cli, command, argv, argc); +} + +int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { + return cli_history(cli, command, argv, argc); +} + +int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc) { + return cli_exit(cli, command, argv, argc); +} + +int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { + return cli_enable(cli, command, argv, argc); +} + +int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { + return cli_disable(cli, command, argv, argc); +} diff --git a/libcli.h b/libcli.h index 3361744..bd3126f 100644 --- a/libcli.h +++ b/libcli.h @@ -247,6 +247,18 @@ 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); +/* + * Expose some previous internal routines. Just in case someone was using those + * with an explicit reference, the original routines (cli_int_*) internally point + * to the newly public routines. + */ +int cli_help(struct cli_def *cli, const char *command, char *argv[], int argc) ; +int cli_history(struct cli_def *cli, const char *command, char *argv[], int argc) ; +int cli_exit(struct cli_def *cli, const char *command, char *argv[], int argc) ; +int cli_quit(struct cli_def *cli, const char *command, char *argv[], int argc) ; +int cli_enable(struct cli_def *cli, const char *command, char *argv[], int argc) ; +int cli_disable(struct cli_def *cli, const char *command, char *argv[], int argc) ; + #ifdef __cplusplus } #endif diff --git a/libcli.spec b/libcli.spec index 88e6de1..46d7cd5 100644 --- a/libcli.spec +++ b/libcli.spec @@ -1,7 +1,7 @@ -Version: 1.10.0 +Version: 1.10.1 Summary: Cisco-like telnet command-line library Name: libcli -Release: 2 +Release: 1 License: LGPL Group: Library/Communication Source: %{name}-%{version}.tar.gz @@ -67,6 +67,11 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Wed Jul 30 2019 Rob Sanders 1.10.1-1 +- Bump version +- Renamed cli_int_[help|history|exit|quit|enable|disable] to + same routine minut '_int_', and exposed in libcli.h. Retained + old command pointing to new command for backward compatability * 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) From 8e6c09a66e543cbd26ddb1a55b6ea8f50c06e806 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 31 Jul 2019 13:41:47 -0400 Subject: [PATCH 02/21] Fix corner case in buildmode where user can exit without entering any additional optargs --- libcli.c | 13 ++++++++++--- libcli.h | 1 + libcli.spec | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/libcli.c b/libcli.c index fae5c94..772afdf 100644 --- a/libcli.c +++ b/libcli.c @@ -1027,9 +1027,12 @@ int cli_loop(struct cli_def *cli, int sockfd) { /* * 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. + * while a command is being evaluated/executed. Also explicitly set the disallow_buildmode flag based on whether or + * not cli->buildmode is NULL or not. The cli->buildmode flag can be changed during process, but the enable/disable + * needs to be set before any processing is entered. */ cli->transient_mode = cli->mode; + cli->disallow_buildmode = (cli->buildmode) ? 1 : 0; if (cli->showprompt) { if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) _write(sockfd, "\r\n", 2); @@ -2402,7 +2405,11 @@ int cli_int_execute_buildmode(struct cli_def *cli) { if (rc == CLI_OK) { cli_int_free_buildmode(cli); cli_add_history(cli, cmdline); + // disallow processing of buildmode so we don't wind up in a potential loop + // main loop will also set as required + cli->disallow_buildmode=1; rc = cli_run_command(cli, cmdline); + } free_z(cmdline); cli_int_free_buildmode(cli); @@ -3098,8 +3105,8 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage 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) { + // Only process CLI_CMD_ALLOW_BUILDMODE if we're not already in buildmode, parsing command (stage 0), and this is the last word + if (!cli->disallow_buildmode && (stage->status == CLI_OK) && (oaptr->flags & CLI_CMD_ALLOW_BUILDMODE) && is_last_word) { stage->status = cli_int_enter_buildmode(cli, stage, value); goto done; } diff --git a/libcli.h b/libcli.h index bd3126f..afec7b3 100644 --- a/libcli.h +++ b/libcli.h @@ -81,6 +81,7 @@ struct cli_def { void *user_context; struct cli_optarg_pair *found_optargs; int transient_mode; + int disallow_buildmode; struct cli_pipeline *pipeline; struct cli_buildmode *buildmode; }; diff --git a/libcli.spec b/libcli.spec index 46d7cd5..582c7b5 100644 --- a/libcli.spec +++ b/libcli.spec @@ -72,6 +72,8 @@ rm -rf $RPM_BUILD_ROOT - Renamed cli_int_[help|history|exit|quit|enable|disable] to same routine minut '_int_', and exposed in libcli.h. Retained old command pointing to new command for backward compatability +- Fix coerner case in buildmode where no extra settings specified + and user enters 'exit' * 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) From 8c0f1fbaf592723e6626a75b3df26f928b9f351d Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 31 Jul 2019 13:42:36 -0400 Subject: [PATCH 03/21] Update clitest with better example for processing optargs with the 'CLI_CMD_OPTION_MULTIPLE' flag set --- clitest.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clitest.c b/clitest.c index dc9f8b2..eacf329 100644 --- a/clitest.c +++ b/clitest.c @@ -184,16 +184,15 @@ int cmd_perimeter(struct cli_def *cli, const char *command, char *argv[], int ar int i = 1, numSides = 0; int perimeter = 0; int verbose_count = 0; - char *verboseArg = NULL; + char *verboseArg; 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))); + verboseArg = NULL; + while ((verboseArg = cli_get_optarg_value(cli, "verbose", verboseArg))) { + verbose_count++; } cli_print(cli, "verbose argument was seen %d times", verbose_count); From 50563842d891bb5222c2da25a5a8409bdcf93c24 Mon Sep 17 00:00:00 2001 From: RobSanders Date: Thu, 1 Aug 2019 14:40:59 -0400 Subject: [PATCH 04/21] Rename buildmode 'exit' -> 'execute' --- libcli.c | 6 +++--- libcli.spec | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libcli.c b/libcli.c index 772afdf..be951bd 100644 --- a/libcli.c +++ b/libcli.c @@ -2276,7 +2276,7 @@ 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/show/unset - if (!strcmp(optarg->name, "cancel") || !strcmp(optarg->name, "exit") || !strcmp(optarg->name, "show") || + if (!strcmp(optarg->name, "cancel") || !strcmp(optarg->name, "execute") || !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; @@ -2315,8 +2315,8 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag // 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, - "Exit and execute command"); + cli_int_register_buildmode_command(cli, NULL, "execute", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, + "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, diff --git a/libcli.spec b/libcli.spec index 582c7b5..db37f7d 100644 --- a/libcli.spec +++ b/libcli.spec @@ -74,6 +74,7 @@ rm -rf $RPM_BUILD_ROOT old command pointing to new command for backward compatability - Fix coerner case in buildmode where no extra settings specified and user enters 'exit' +- Rename buildmode 'exit' command to 'execute' based on feedback * 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) From 33d5d132244ea50fc4a01dddee8a0d85e7b442e3 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Thu, 1 Aug 2019 14:48:59 -0400 Subject: [PATCH 05/21] Fix changelog entry in spec --- libcli.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/libcli.spec b/libcli.spec index db37f7d..1899102 100644 --- a/libcli.spec +++ b/libcli.spec @@ -75,6 +75,7 @@ rm -rf $RPM_BUILD_ROOT - Fix coerner case in buildmode where no extra settings specified and user enters 'exit' - Rename buildmode 'exit' command to 'execute' based on feedback + * 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) From 7cb0b51a257785567828c73eb042960010e228e2 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 7 Aug 2019 14:10:56 -0400 Subject: [PATCH 06/21] Major rework of how 'help' lines are formatted All help lines consist of the name and helpstr. This treats allows each line to be 'wrapped' and collimated so each partial piece of helpstr will be left aligned with each other. Wrapping is done assuming an 80 col width, and if a line is wrapped it will attempt to split it at the last white space in that segment. If none found, then it prints what it can. Then it also breaks at the first '\n' or '\r'. Additionally, for optarg help, we can have 'specific' help messages in addition to the general help for the optarg. Look at the clitest program for an example (both 'triangle' and 'rectangle' have specific help msgs for the 'shape' optarg. These specific help messages are tab ('\t') separated and occur after the general help message. A specific message will only be shown if 'could' be a match. --- clitest.c | 6 +-- libcli.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/clitest.c b/clitest.c index eacf329..fb03f10 100644 --- a/clitest.c +++ b/clitest.c @@ -366,17 +366,17 @@ void run_child(int x) { // 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"); + "Calculate perimeter of polygon\nhas embedded newline\nand_a_really_long_line_that_is_much_longer_than_80_columns_to_show_that_wrap_case"); 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); + "Set verbose flagwith some humongously long string \nwithout any embedded newlines in it to test with", 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, + "Specify shape(shows subtext on help)\ttriangle\tSpecify a triangle\trectangle\tspecify a rectangle", shape_completor, shape_validator, shape_transient_eval); cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 1 length", NULL, side_length_validator, NULL); diff --git a/libcli.c b/libcli.c index be951bd..27b100b 100644 --- a/libcli.c +++ b/libcli.c @@ -166,6 +166,7 @@ static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pi inline void cli_int_show_pipeline(struct cli_def *cli, 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 void cli_int_wrap_help_line (char *nameptr, char *helpptr, struct cli_comphelp *comphelp) ; static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; @@ -861,8 +862,9 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } if (lastchar == '?') { - if (asprintf(&strptr, " %-20s %s", c->command, (c->help) ? c->help : "") != -1) { - cli_add_comphelp_entry(comphelp, strptr); + + if (asprintf(&strptr, " %-20s ", c->command) != -1) { + cli_int_wrap_help_line (strptr, c->help, comphelp); free_z(strptr); } } else { @@ -2407,7 +2409,7 @@ int cli_int_execute_buildmode(struct cli_def *cli) { cli_add_history(cli, cmdline); // disallow processing of buildmode so we don't wind up in a potential loop // main loop will also set as required - cli->disallow_buildmode=1; + cli->disallow_buildmode = 1; rc = cli_run_command(cli, cmdline); } @@ -2830,13 +2832,63 @@ int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) return rc; } +/* + * Attemp quick dirty wrapping of helptext taking into account the offset from name, embedded + * cr/lf in helptext, and trying to split on last white-text before the margin + */ +void cli_int_wrap_help_line (char *nameptr, char *helpptr, struct cli_comphelp *comphelp) { + int maxwidth = 80; // temporary assumption, to be fixed later when libcli 'understands' screen dimensions + int availwidth; + int namewidth; + int toprint; + char *crlf; + char *line; + char emptystring[]=""; + namewidth = strlen(nameptr); + availwidth = maxwidth - namewidth; + + if (!helpptr) helpptr = emptystring; + /* + * Now we need to iterate one or more times to only print out at most + * maxwidth - leftwidth characters of helpptr. Note that there are no + * tabs in helpptr, so each 'char' displays as one char + */ + + do { + toprint = strlen(helpptr); + if (toprint > availwidth) { + toprint = availwidth; + while ((toprint>=0) && !isspace(helpptr[toprint])) toprint--; + if ( toprint < 0) { + // if we backed up and found no whitespace, dump as much as we can + toprint = availwidth; + } + } // see if we might have an embedded carriage return or line feed + if ( (crlf = strpbrk(helpptr,"\n\r"))) { + // crlf is a pointer - see if it is 'before' the toprint index + if ((crlf-helpptr) < toprint) { + // ok, crlf is before the wrap, us it + toprint = (crlf-helpptr); + } + } + + if (asprintf(&line, "%*.*s%.*s", namewidth, namewidth, nameptr, toprint, helpptr) < 0) break; + cli_add_comphelp_entry(comphelp, line); + if (nameptr != emptystring) { + nameptr = emptystring; + } + free_z(line); + helpptr += toprint; + // advance to first non whitespace + while (helpptr && isspace(*helpptr)) helpptr++; + } while (*helpptr); +} + 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, @@ -2845,10 +2897,8 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta 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 *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; @@ -2892,17 +2942,71 @@ 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(&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", 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", tname, (optarg->help) ? optarg->help : "", allow_buildmode) != -1) { - cli_add_comphelp_entry(comphelp, tptr); - free_z(tptr); + if (lastchar == '?') { + /* + * Note - help is a bit complex, and we could optimize it. But it isn't done often, + * so we're always going to do it on the fly. Help output will consist of a 'name' and a 'text' + * field and some formatting. The 'help' member of an optarg is a single string, that may contain + * both embedded tabs and newlines. The tab characters (and end-of-string) are used as delimiters. + * On the first pass through the string the 'name' will be the name of the optarg, and the 'text' + * will be the first field. There after we pull name/text delimited pairs until nothing is left. + * The first pass through will be indented 2 spaces on the left with the formated name occupying + * 20 spaces (expanding if more than 20). If the command is a 'buildmode' command the first + * character of the 'text' will be an asterisk. The 'rest' of the line (assuming an 80 character ' + * wide line for now) will be used to wrap the 'text' field honoring embedded newlines, and trying to + * wrap on nearest preceeding whitespace when it hits a boundary. Subsequent lines will be indented + * by an additional 2 spaces, and will drop the asterisk. + */ + char *working = NULL; + char *nameptr = NULL; + char *helpptr = NULL; + char *saveptr = NULL; + char *tname = NULL; + int indent = 2; + int helplen; + char emptystring[] = ""; + // print out actual text into a working buffer that we can then call 'strtok_r' on + + helplen = asprintf(&working, "%s%s%s%s%s", + (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", + (help_insert) ? "enter '" : "", + (help_insert) ? optarg->name : "", + (help_insert) ? "' to set " : "", + (help_insert) ? optarg->name : optarg->help); + + // preload out nptr,hptr fields; + nameptr = optarg->name; + + if (helplen < 0) { + helpptr = emptystring; + working = NULL; } - free_z(tname); + else { + helpptr = strtok_r(working, "\t", &saveptr); + } + + // break things up into tab separated entities - always show the first entry + do { + char *leftcolumn; + if (asprintf(&tname, "%s%s%s", delim_start, nameptr, delim_end) == -1) break; + if (asprintf(&leftcolumn, "%*.*s%-20s " , indent, indent, "", tname) == -1) break; + + cli_int_wrap_help_line(leftcolumn, helpptr, comphelp); + + // clear out any delimiter settings and set indent for any subtext + delim_start = DELIM_NONE; + delim_end = DELIM_NONE; + indent = 4; + free_z(tname); + free_z(leftcolumn); + + // we may not need to show all off the 'extra help', so loop here + do { + nameptr = strtok_r(NULL, "\t", &saveptr); + helpptr = strtok_r(NULL, "\t", &saveptr); + } while (working && nameptr && helpptr && (anchor_word && (strncmp(anchor_word, nameptr, strlen(anchor_word))))); + } while (working && nameptr && helpptr); + free_z(working); } else if (lastchar == CTRL('I')) { if (get_completions) { (*get_completions)(cli, optarg->name, next_word, comphelp); From ac086d0e6c713a7c4ce20a06ac0d02e24f546926 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 7 Aug 2019 14:49:47 -0400 Subject: [PATCH 07/21] Update spec --- libcli.spec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libcli.spec b/libcli.spec index 1899102..cc1aebd 100644 --- a/libcli.spec +++ b/libcli.spec @@ -67,6 +67,13 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Wed Aug 7 2019 Rob Sanders 1.10.1-1 +- Rework how help text is formatted to allow for word wrapping + and embedded newlines +- Rework optarg help text to allow the optarg to have specific + help messages based on user input - look at clitest at the + 'shape' optarg examples. + * Wed Jul 30 2019 Rob Sanders 1.10.1-1 - Bump version - Renamed cli_int_[help|history|exit|quit|enable|disable] to From d4979ef63b7680fb21a7f5d61c19ea67480875c5 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 7 Aug 2019 14:51:05 -0400 Subject: [PATCH 08/21] Update spec --- libcli.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libcli.spec b/libcli.spec index cc1aebd..50837d1 100644 --- a/libcli.spec +++ b/libcli.spec @@ -70,6 +70,8 @@ rm -rf $RPM_BUILD_ROOT * Wed Aug 7 2019 Rob Sanders 1.10.1-1 - Rework how help text is formatted to allow for word wrapping and embedded newlines +- Rework how 'buildmode' is show for help messages so it uses + a single '*' as the first char of the help text. - Rework optarg help text to allow the optarg to have specific help messages based on user input - look at clitest at the 'shape' optarg examples. From e49127885ca119f70092a9d17d06068e2ce8610e Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Thu, 8 Aug 2019 08:05:20 -0400 Subject: [PATCH 09/21] Updated w/changes as per maintainer --- libcli.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/libcli.c b/libcli.c index 27b100b..558a1d8 100644 --- a/libcli.c +++ b/libcli.c @@ -166,7 +166,7 @@ static int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pi inline void cli_int_show_pipeline(struct cli_def *cli, 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 void cli_int_wrap_help_line (char *nameptr, char *helpptr, struct cli_comphelp *comphelp) ; +static void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp); static ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; @@ -862,9 +862,8 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } if (lastchar == '?') { - if (asprintf(&strptr, " %-20s ", c->command) != -1) { - cli_int_wrap_help_line (strptr, c->help, comphelp); + cli_int_wrap_help_line(strptr, c->help, comphelp); free_z(strptr); } } else { @@ -2836,7 +2835,7 @@ int cli_int_execute_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline) * Attemp quick dirty wrapping of helptext taking into account the offset from name, embedded * cr/lf in helptext, and trying to split on last white-text before the margin */ -void cli_int_wrap_help_line (char *nameptr, char *helpptr, struct cli_comphelp *comphelp) { +void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp) { int maxwidth = 80; // temporary assumption, to be fixed later when libcli 'understands' screen dimensions int availwidth; int namewidth; @@ -2859,7 +2858,7 @@ void cli_int_wrap_help_line (char *nameptr, char *helpptr, struct cli_comphelp * if (toprint > availwidth) { toprint = availwidth; while ((toprint>=0) && !isspace(helpptr[toprint])) toprint--; - if ( toprint < 0) { + if (toprint < 0) { // if we backed up and found no whitespace, dump as much as we can toprint = availwidth; } @@ -2867,17 +2866,16 @@ void cli_int_wrap_help_line (char *nameptr, char *helpptr, struct cli_comphelp * if ( (crlf = strpbrk(helpptr,"\n\r"))) { // crlf is a pointer - see if it is 'before' the toprint index if ((crlf-helpptr) < toprint) { - // ok, crlf is before the wrap, us it + // ok, crlf is before the wrap, so have line break here. toprint = (crlf-helpptr); } } if (asprintf(&line, "%*.*s%.*s", namewidth, namewidth, nameptr, toprint, helpptr) < 0) break; cli_add_comphelp_entry(comphelp, line); - if (nameptr != emptystring) { - nameptr = emptystring; - } free_z(line); + + nameptr = emptystring; helpptr += toprint; // advance to first non whitespace while (helpptr && isspace(*helpptr)) helpptr++; @@ -3003,7 +3001,7 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta // we may not need to show all off the 'extra help', so loop here do { nameptr = strtok_r(NULL, "\t", &saveptr); - helpptr = strtok_r(NULL, "\t", &saveptr); + helpptr = strtok_r(NULL, "\t", &saveptr); } while (working && nameptr && helpptr && (anchor_word && (strncmp(anchor_word, nameptr, strlen(anchor_word))))); } while (working && nameptr && helpptr); free_z(working); From b9b1bd50b9b8f9f570c395240f4b592268289558 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Thu, 15 Aug 2019 16:09:47 -0400 Subject: [PATCH 10/21] Rework slightly extended help - new comand for cleaner use --- clitest.c | 6 +++-- libcli.c | 67 +++++++++++++++++++++++++++++++++++++------------------ libcli.h | 1 + 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/clitest.c b/clitest.c index fb03f10..ccf9625 100644 --- a/clitest.c +++ b/clitest.c @@ -376,8 +376,10 @@ void run_child(int x) { 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(shows subtext on help)\ttriangle\tSpecify a triangle\trectangle\tspecify a rectangle", shape_completor, shape_validator, - shape_transient_eval); + "Specify shape(shows subtext on help)", shape_completor, shape_validator, shape_transient_eval); + cli_optarg_addhelp(c, "shape", "triangle", "specify a triangle"); + cli_optarg_addhelp(c, "shape", "rectangle", "pecify a rectangle"); + 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 558a1d8..1499443 100644 --- a/libcli.c +++ b/libcli.c @@ -2095,6 +2095,23 @@ void cli_free_optarg(struct cli_optarg *optarg) { free_z(optarg); } +int cli_optarg_addhelp(struct cli_command *cmd, const char *optargname, const char *helpname, const char *helptext) { + char *tstr; + struct cli_optarg *optarg; + + for (optarg = cmd->optargs; optarg && !strcmp(optarg->name, optargname); optarg = optarg->next) ; + + // put a vertical tab (\v), the new helpname, a horizontal tab (\t), and then the new help text + if ((!optarg) || (asprintf(&tstr,"%s\v%s\t%s" , optarg->help, helpname, helptext) == -1)) { + return CLI_ERROR; + } else { + free(optarg->help); + optarg->help = tstr; + } + return CLI_OK; +} + + 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 *), @@ -2860,7 +2877,7 @@ void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *c while ((toprint>=0) && !isspace(helpptr[toprint])) toprint--; if (toprint < 0) { // if we backed up and found no whitespace, dump as much as we can - toprint = availwidth; + toprint = availwidth; } } // see if we might have an embedded carriage return or line feed if ( (crlf = strpbrk(helpptr,"\n\r"))) { @@ -2943,11 +2960,11 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta if (lastchar == '?') { /* * Note - help is a bit complex, and we could optimize it. But it isn't done often, - * so we're always going to do it on the fly. Help output will consist of a 'name' and a 'text' - * field and some formatting. The 'help' member of an optarg is a single string, that may contain - * both embedded tabs and newlines. The tab characters (and end-of-string) are used as delimiters. - * On the first pass through the string the 'name' will be the name of the optarg, and the 'text' - * will be the first field. There after we pull name/text delimited pairs until nothing is left. + * so we're always going to do it on the fly. + * Help will consist of '\v' separated lines. Each line except the first is also '\t' + * separated into the name/text fields. If a line does not have a '\t' separated then the + * name will be the name of the optarg, and the help will be that entire line. The *first* + * does get some tweaks to how the name and help is displayed. * The first pass through will be indented 2 spaces on the left with the formated name occupying * 20 spaces (expanding if more than 20). If the command is a 'buildmode' command the first * character of the 'text' will be an asterisk. The 'rest' of the line (assuming an 80 character ' @@ -2958,30 +2975,33 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta char *working = NULL; char *nameptr = NULL; char *helpptr = NULL; - char *saveptr = NULL; + char *lineptr = NULL; + char *savelineptr = NULL; + char *savetabptr = NULL; char *tname = NULL; int indent = 2; int helplen; char emptystring[] = ""; - // print out actual text into a working buffer that we can then call 'strtok_r' on + /* + * Print out actual text into a working buffer that we can then call 'strtok_r' on it. This lets + * us prepend some optional fields nice and easily. At this point it is one big string. + */ helplen = asprintf(&working, "%s%s%s%s%s", - (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", - (help_insert) ? "enter '" : "", - (help_insert) ? optarg->name : "", - (help_insert) ? "' to set " : "", - (help_insert) ? optarg->name : optarg->help); - - // preload out nptr,hptr fields; + (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", + (help_insert) ? "enter '" : "", + (help_insert) ? optarg->name : "", + (help_insert) ? "' to set " : "", + (help_insert) ? optarg->name : optarg->help); + + // pull the first line + helpptr = strtok_r(working, "\v", &savelineptr); nameptr = optarg->name; if (helplen < 0) { helpptr = emptystring; working = NULL; } - else { - helpptr = strtok_r(working, "\t", &saveptr); - } // break things up into tab separated entities - always show the first entry do { @@ -3000,10 +3020,13 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta // we may not need to show all off the 'extra help', so loop here do { - nameptr = strtok_r(NULL, "\t", &saveptr); - helpptr = strtok_r(NULL, "\t", &saveptr); - } while (working && nameptr && helpptr && (anchor_word && (strncmp(anchor_word, nameptr, strlen(anchor_word))))); - } while (working && nameptr && helpptr); + lineptr = strtok_r(NULL, "\v", &savelineptr); + if (lineptr) { + nameptr = strtok_r(lineptr, "\t", &savetabptr); + helpptr = strtok_r(NULL, "\t", &savetabptr); + } + } while (lineptr && nameptr && helpptr && (anchor_word && (strncmp(anchor_word, nameptr, strlen(anchor_word))))); + } while (lineptr && nameptr && helpptr); free_z(working); } else if (lastchar == CTRL('I')) { if (get_completions) { diff --git a/libcli.h b/libcli.h index afec7b3..ccc8cda 100644 --- a/libcli.h +++ b/libcli.h @@ -238,6 +238,7 @@ int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, in 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 *)); +int cli_optarg_addhelp(struct cli_command *cmd, const char *optargname, const char *helpname, const char *helptext); 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); From 469ab454583080ea692ca28a16fa2bb0c9d0be5b Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 21 Aug 2019 16:44:44 -0400 Subject: [PATCH 11/21] Fix serious bug if a command of only spaces or just spaces after a pipe character --- libcli.c | 12 +++++++++++- libcli.spec | 5 ++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libcli.c b/libcli.c index 1499443..b56bdd9 100644 --- a/libcli.c +++ b/libcli.c @@ -2688,9 +2688,19 @@ int cli_int_validate_pipeline(struct cli_def *cli, struct cli_pipeline *pipeline cli->pipeline = pipeline; cli->found_optargs = NULL; + + // If the line is totally empty this is not an error, but we need to return + // CLI_ERROR to avoid processing it + if (pipeline->num_words == 0) return CLI_ERROR; + for (i = 0; i < pipeline->num_stages; i++) { + // And double check each stage for an empty line - this *is* an error + if (pipeline->stage[i].num_words == 0) { + cli_error(cli, "Empty command given"); + return CLI_ERROR; + } + // 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) diff --git a/libcli.spec b/libcli.spec index 50837d1..85bc6fe 100644 --- a/libcli.spec +++ b/libcli.spec @@ -1,7 +1,7 @@ Version: 1.10.1 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,9 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Wed Aug 21 2019 Rob Sanders 10.10.1-2 +- Bug for processing an empty line, or empty command after a pipe character + * Wed Aug 7 2019 Rob Sanders 1.10.1-1 - Rework how help text is formatted to allow for word wrapping and embedded newlines From 702a7e9d3306240c2a09c6269aaf74a77d158bde Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 21 Aug 2019 16:47:31 -0400 Subject: [PATCH 12/21] Fix spec typo --- libcli.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcli.spec b/libcli.spec index 85bc6fe..0a02917 100644 --- a/libcli.spec +++ b/libcli.spec @@ -67,7 +67,7 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog -* Wed Aug 21 2019 Rob Sanders 10.10.1-2 +* Wed Aug 21 2019 Rob Sanders 1.10.1-2 - Bug for processing an empty line, or empty command after a pipe character * Wed Aug 7 2019 Rob Sanders 1.10.1-1 From 56ab3ebc8594f5df8785b3a68d3bff75d8966af3 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Wed, 21 Aug 2019 19:50:03 -0400 Subject: [PATCH 13/21] Bump to 1.10.2-1 rather than 1.10.1-2 --- Makefile | 2 +- libcli.h | 2 +- libcli.spec | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9c8a985..58f8bc8 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PREFIX = /usr/local MAJOR = 1 MINOR = 10 -REVISION = 1 +REVISION = 2 LIB = libcli.so LIB_STATIC = libcli.a diff --git a/libcli.h b/libcli.h index ccc8cda..bf23ab4 100644 --- a/libcli.h +++ b/libcli.h @@ -13,7 +13,7 @@ extern "C" { #define LIBCLI_VERSION_MAJOR 1 #define LIBCLI_VERISON_MINOR 10 -#define LIBCLI_VERISON_REVISION 0 +#define LIBCLI_VERISON_REVISION 2 #define LIBCLI_VERSION ((LIBCLI_VERSION_MAJOR << 16) | (LIBCLI_VERSION_MINOR << 8) | LIBCLI_VERSION_REVISION) #define CLI_OK 0 diff --git a/libcli.spec b/libcli.spec index 0a02917..7af6185 100644 --- a/libcli.spec +++ b/libcli.spec @@ -1,7 +1,7 @@ -Version: 1.10.1 +Version: 1.10.2 Summary: Cisco-like telnet command-line library Name: libcli -Release: 2 +Release: 1 License: LGPL Group: Library/Communication Source: %{name}-%{version}.tar.gz @@ -67,8 +67,9 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog -* Wed Aug 21 2019 Rob Sanders 1.10.1-2 +* Wed Aug 21 2019 Rob Sanders 1.10.2-1 - Bug for processing an empty line, or empty command after a pipe character +- Bump version to 1.10.2-1 * Wed Aug 7 2019 Rob Sanders 1.10.1-1 - Rework how help text is formatted to allow for word wrapping From 19212681d9d34f48e9c47722cfb6302b47d39aa7 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 3 Sep 2019 14:05:10 -0400 Subject: [PATCH 14/21] Fixes for buildmode help output, bug fix for extra help for optargs --- libcli.c | 55 +++++++++++++++++++++++++++++++++++------------------ libcli.h | 1 + libcli.spec | 6 ++++++ 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/libcli.c b/libcli.c index b56bdd9..3db184d 100644 --- a/libcli.c +++ b/libcli.c @@ -146,12 +146,12 @@ static int cli_int_unregister_command_core(struct cli_def *cli, const char *comm 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); + int (*callback)(struct cli_def *cli, const char *, char **, int), int flags, 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_execute_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, @@ -815,7 +815,10 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar int command_type; struct cli_pipeline *pipeline = NULL; struct cli_pipeline_stage *stage; - + char delims[][2][2] = { { "" , "" } , // No extra chars required for text + { "[" , "]" }, // Enclose text with '[] + }; + int delimIdx; if (!(pipeline = cli_int_generate_pipeline(cli, command))) goto out; stage = &pipeline->stage[pipeline->num_stages - 1]; @@ -833,6 +836,7 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar for (c = cli->commands, i = 0; c && i < stage->num_words; c = n) { char *strptr = NULL; + char *nameptr = NULL; n = c->next; if (c->command_type != command_type) continue; @@ -862,9 +866,22 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } if (lastchar == '?') { - if (asprintf(&strptr, " %-20s ", c->command) != -1) { - cli_int_wrap_help_line(strptr, c->help, comphelp); - free_z(strptr); + delimIdx = 0; // assume no '[]' required + + // Note that buildmode commands need to see if that command is some optinal value + + if (command_type == CLI_BUILDMODE_COMMAND) { + if (c->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) { + delimIdx = 1; + } + } + if (asprintf(&nameptr, "%s%s%s" , delims[delimIdx][0], c->command, delims[delimIdx][1]) != -1 ) + { + if (asprintf(&strptr, " %-20s %s ", nameptr, c->command) != -1) { + cli_int_wrap_help_line(strptr, c->help, comphelp); + free_z(strptr); + } + free(nameptr); } } else { cli_add_comphelp_entry(comphelp, c->command); @@ -2099,7 +2116,7 @@ int cli_optarg_addhelp(struct cli_command *cmd, const char *optargname, const ch char *tstr; struct cli_optarg *optarg; - for (optarg = cmd->optargs; optarg && !strcmp(optarg->name, optargname); optarg = optarg->next) ; + for (optarg = cmd->optargs; optarg && strcmp(optarg->name, optargname); optarg = optarg->next) ; // put a vertical tab (\v), the new helpname, a horizontal tab (\t), and then the new help text if ((!optarg) || (asprintf(&tstr,"%s\v%s\t%s" , optarg->help, helpname, helptext) == -1)) { @@ -2305,7 +2322,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag 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))) { + optarg->flags, 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); @@ -2316,13 +2333,13 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag } 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)) { + optarg->flags, optarg->privilege, cli->mode, optarg->help)) { 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)) { + optarg->flags, optarg->privilege, cli->mode, optarg->help)) { rc = CLI_BUILDMODE_ERROR; goto out; } @@ -2331,13 +2348,13 @@ 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 - cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, PRIVILEGE_UNPRIVILEGED, + cli_int_register_buildmode_command(cli, NULL, "cancel", cli_int_buildmode_cancel_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); - cli_int_register_buildmode_command(cli, NULL, "execute", cli_int_buildmode_exit_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, + cli_int_register_buildmode_command(cli, NULL, "execute", cli_int_buildmode_execute_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Execute command"); - cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, PRIVILEGE_UNPRIVILEGED, cli->mode, + cli_int_register_buildmode_command(cli, NULL, "show", cli_int_buildmode_show_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Show current settings"); - c = cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, PRIVILEGE_UNPRIVILEGED, + c = cli_int_register_buildmode_command(cli, NULL, "unset", cli_int_buildmode_unset_cback, 0, 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); @@ -2354,12 +2371,13 @@ int cli_int_unregister_buildmode_command(struct cli_def *cli, const char *comman 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) { + int flags, int privilege, int mode, const char *help) { struct cli_command *c; if (!command) return NULL; if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL; + c->flags = flags; c->callback = callback; c->next = NULL; if (!(c->command = strdup(command))) { @@ -2509,7 +2527,7 @@ int cli_int_buildmode_cancel_cback(struct cli_def *cli, const char *command, cha return rc; } -int cli_int_buildmode_exit_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { +int cli_int_buildmode_execute_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXIT; if (argc > 0) { @@ -2792,7 +2810,8 @@ struct cli_pipeline *cli_int_generate_pipeline(struct cli_def *cli, const char * if (cli->buildmode) { // Can't allow filters in buildmode commands cli_int_free_pipeline(pipeline); - return NULL; + cli_error(cli, "\nPipelines are not allowed in buildmode"); + return NULL; } stage->stage_num = pipeline->num_stages; stage++; @@ -2999,7 +3018,7 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta */ helplen = asprintf(&working, "%s%s%s%s%s", (optarg->flags & CLI_CMD_ALLOW_BUILDMODE) ? "* " : "", - (help_insert) ? "enter '" : "", + (help_insert) ? "type '" : "", (help_insert) ? optarg->name : "", (help_insert) ? "' to set " : "", (help_insert) ? optarg->name : optarg->help); diff --git a/libcli.h b/libcli.h index bf23ab4..136b6f0 100644 --- a/libcli.h +++ b/libcli.h @@ -114,6 +114,7 @@ struct cli_command { 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; + int flags; }; struct cli_comphelp { diff --git a/libcli.spec b/libcli.spec index 7af6185..09805f4 100644 --- a/libcli.spec +++ b/libcli.spec @@ -67,6 +67,12 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Tue Sep 3 2019 Rob Sanders 1.10.2-1 +- Fix bug in cli_optarg_addhelp() +- Change 'enter' to 'type' for buildmode commands autogenerated help +- Alter 'buildmode' help settings to indicate if a buildmode setting + is optional by enclosing in '[]' + * Wed Aug 21 2019 Rob Sanders 1.10.2-1 - Bug for processing an empty line, or empty command after a pipe character - Bump version to 1.10.2-1 From 5abee959bf84798318f15d88534efcfc3a96cfac Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 3 Sep 2019 14:59:27 -0400 Subject: [PATCH 15/21] Refactor buildmode help to follow optarg help for delimeters on help entries --- libcli.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/libcli.c b/libcli.c index 3db184d..2d35020 100644 --- a/libcli.c +++ b/libcli.c @@ -167,7 +167,14 @@ inline void cli_int_show_pipeline(struct cli_def *cli, struct cli_pipeline *pipe 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 void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *comphelp); - + +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 ssize_t _write(int fd, const void *buf, size_t count) { size_t written = 0; ssize_t thisTime = 0; @@ -815,10 +822,9 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar int command_type; struct cli_pipeline *pipeline = NULL; struct cli_pipeline_stage *stage; - char delims[][2][2] = { { "" , "" } , // No extra chars required for text - { "[" , "]" }, // Enclose text with '[] - }; - int delimIdx; + char *delim_start = DELIM_NONE ; + char *delim_end = DELIM_NONE; + if (!(pipeline = cli_int_generate_pipeline(cli, command))) goto out; stage = &pipeline->stage[pipeline->num_stages - 1]; @@ -866,16 +872,18 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } if (lastchar == '?') { - delimIdx = 0; // assume no '[]' required + delim_start = DELIM_NONE; + delim_end = DELIM_NONE; // Note that buildmode commands need to see if that command is some optinal value if (command_type == CLI_BUILDMODE_COMMAND) { if (c->flags & (CLI_CMD_OPTIONAL_FLAG | CLI_CMD_OPTIONAL_ARGUMENT)) { - delimIdx = 1; + delim_start = DELIM_OPT_START; + delim_end = DELIM_OPT_END; } } - if (asprintf(&nameptr, "%s%s%s" , delims[delimIdx][0], c->command, delims[delimIdx][1]) != -1 ) + if (asprintf(&nameptr, "%s%s%s" , delim_start, c->command, delim_end) != -1 ) { if (asprintf(&strptr, " %-20s %s ", nameptr, c->command) != -1) { cli_int_wrap_help_line(strptr, c->help, comphelp); @@ -2928,12 +2936,6 @@ void cli_int_wrap_help_line(char *nameptr, char *helpptr, struct cli_comphelp *c } while (*helpptr); } -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 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) { From 0d1197757ce591ff1579e9c23195174fae8cdee2 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 6 Sep 2019 08:52:21 -0400 Subject: [PATCH 16/21] Fix issue where extra help was not showing correctly for optional arguments --- clitest.c | 17 ++++++++++++++++- libcli.c | 3 +-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/clitest.c b/clitest.c index ccf9625..277194d 100644 --- a/clitest.c +++ b/clitest.c @@ -266,7 +266,7 @@ int shape_transient_eval(struct cli_def *cli, const char *name, const char *valu const char *KnownColors[] = {"black", "white", "gray", "red", "blue", "green", "lightred", "lightblue", "lightgreen", "darkred", - "darkblue", "darkgree", "lavender", "yellow", NULL}; + "darkblue", "darkgreen", "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 @@ -373,6 +373,21 @@ void run_child(int x) { "Set verbose flagwith some humongously long string \nwithout any embedded newlines in it to test with", NULL, NULL, NULL); cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", color_completor, color_validator, NULL); + cli_optarg_addhelp(c, "color", "black" , "the color 'black'"); + cli_optarg_addhelp(c, "color", "white" , "the color 'white'"); + cli_optarg_addhelp(c, "color", "gray" , "the color 'gray'"); + cli_optarg_addhelp(c, "color", "red" , "the color 'red'"); + cli_optarg_addhelp(c, "color", "blue" , "the color 'blue'"); + cli_optarg_addhelp(c, "color", "green" , "the color 'green'"); + cli_optarg_addhelp(c, "color", "lightred" , "the color 'lightred'"); + cli_optarg_addhelp(c, "color", "lightblue" , "the color 'lightblue'"); + cli_optarg_addhelp(c, "color", "lightgreen" , "the color 'lightgreen'"); + cli_optarg_addhelp(c, "color", "darkred" , "the color 'darkred'"); + cli_optarg_addhelp(c, "color", "darkblue" , "the color 'darkblue'"); + cli_optarg_addhelp(c, "color", "darkgreen" , "the color 'darkgreen'"); + cli_optarg_addhelp(c, "color", "lavender" , "the color 'lavender'"); + cli_optarg_addhelp(c, "color", "yellow" , "the color 'yellow'"); + 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, diff --git a/libcli.c b/libcli.c index 2d35020..aa213be 100644 --- a/libcli.c +++ b/libcli.c @@ -2941,7 +2941,6 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta const char *next_word) { int help_insert = 0; char *delim_start = DELIM_NONE; - char *delim_end = DELIM_NONE; int (*get_completions)(struct cli_def *, const char *, const char *, struct cli_comphelp *) = NULL; char *tptr = NULL; @@ -3056,7 +3055,7 @@ static void cli_get_optarg_comphelp(struct cli_def *cli, struct cli_optarg *opta nameptr = strtok_r(lineptr, "\t", &savetabptr); helpptr = strtok_r(NULL, "\t", &savetabptr); } - } while (lineptr && nameptr && helpptr && (anchor_word && (strncmp(anchor_word, nameptr, strlen(anchor_word))))); + } while (lineptr && nameptr && helpptr && (next_word && (strncmp(next_word, nameptr, strlen(next_word))))); } while (lineptr && nameptr && helpptr); free_z(working); } else if (lastchar == CTRL('I')) { From de1cb0734ac84b3bf57c4accacedce67c853bcc6 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 6 Sep 2019 15:38:10 -0400 Subject: [PATCH 17/21] Fix bug if buildmode unset has no value, add buildmode unset completor/validator, fix help messages for buildmode, and add dynamic help for buildmode unset --- clitest.c | 3 +- libcli.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/clitest.c b/clitest.c index 277194d..ed9c48f 100644 --- a/clitest.c +++ b/clitest.c @@ -249,6 +249,7 @@ int verbose_validator(struct cli_def *cli, const char *name, const char *value) return CLI_OK; } +// note that we're setting a 'custom' optarg tag/value pair as an example here int shape_transient_eval(struct cli_def *cli, const char *name, const char *value) { printf("shape_transient_eval called with <%s>\n", value); if (!strcmp(value, "rectangle")) { @@ -343,7 +344,7 @@ void run_child(int x) { 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/libcli.c b/libcli.c index aa213be..1b395d1 100644 --- a/libcli.c +++ b/libcli.c @@ -147,6 +147,7 @@ static int cli_int_unregister_buildmode_command(struct cli_def *cli, const char 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 flags, int privilege, int mode, const char *help); +static void cli_int_buildmode_reset_unset_help(struct cli_def *cli); 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); @@ -885,7 +886,7 @@ void cli_get_completions(struct cli_def *cli, const char *command, char lastchar } if (asprintf(&nameptr, "%s%s%s" , delim_start, c->command, delim_end) != -1 ) { - if (asprintf(&strptr, " %-20s %s ", nameptr, c->command) != -1) { + if (asprintf(&strptr, " %-20s", nameptr) != -1) { cli_int_wrap_help_line(strptr, c->help, comphelp); free_z(strptr); } @@ -1693,6 +1694,7 @@ int cli_loop(struct cli_def *cli, int sockfd) { // Recall all located optargs cli->found_optargs = cli->buildmode->found_optargs; rc = cli_int_execute_buildmode(cli); + break; case CLI_QUIT: break; case CLI_BUILDMODE_START: @@ -2325,7 +2327,7 @@ int cli_int_enter_buildmode(struct cli_def *cli, struct cli_pipeline_stage *stag rc = CLI_BUILDMODE_ERROR; goto out; } - if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE)) continue; + if (optarg->flags & (CLI_CMD_ALLOW_BUILDMODE | CLI_CMD_TRANSIENT_MODE | CLI_CMD_SPOT_CHECK)) continue; if (optarg->mode != cli->mode && optarg->mode != cli->transient_mode) continue; else if (optarg->flags & (CLI_CMD_OPTIONAL_ARGUMENT | CLI_CMD_ARGUMENT)) { @@ -2355,7 +2357,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 + // Now add the 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, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, "Cancel command"); cli_int_register_buildmode_command(cli, NULL, "execute", cli_int_buildmode_execute_cback, 0, PRIVILEGE_UNPRIVILEGED, cli->mode, @@ -2368,6 +2370,8 @@ 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: + // And lastly set the initial help menu for the unset command + cli_int_buildmode_reset_unset_help(cli); if (rc != CLI_BUILDMODE_START) cli_int_free_buildmode(cli); return rc; } @@ -2396,7 +2400,7 @@ struct cli_command *cli_int_register_buildmode_command(struct cli_def *cli, stru c->command_type = CLI_BUILDMODE_COMMAND; c->privilege = privilege; c->mode = mode; - if (help && !(c->help = strdup(help))) { + if (help && !(c->help = strndup(help,strchrnul(help, '\v')-help))) { free(c->command); free(c); return NULL; @@ -2456,7 +2460,6 @@ int cli_int_execute_buildmode(struct cli_def *cli) { } free_z(cmdline); - cli_int_free_buildmode(cli); return rc; } @@ -2484,6 +2487,46 @@ char *cli_int_buildmode_extend_cmdline(char *cmdline, char *word) { return tptr; } +// Any time we set or unset a buildmode setting, we need to regerate the 'help' menu for the unset command +void cli_int_buildmode_reset_unset_help(struct cli_def *cli) { + + struct cli_command *cmd; + + // find the buildmode unset command + for (cmd = cli->commands; cmd ; cmd = cmd->next) { + if ((cmd->command_type == CLI_BUILDMODE_COMMAND) && !strcmp(cmd->command, "unset")) break; + } + + if (cmd) { + struct cli_optarg *optarg; + for (optarg = cmd->optargs; optarg && strcmp(optarg->name, "setting"); optarg = optarg->next) ; + + if (optarg) { + char *endOfMainHelp; + struct cli_optarg_pair *optarg_pair; + /* + * This will ensure that any previously added help is not propogated - this left over space will be freed by the + * cli_optarg_addhelp() calls a few lines down + */ + if ((endOfMainHelp = strchr(optarg->help,'\v'))) *endOfMainHelp = '\0'; + 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->commands; + for (; c; c = c->next) { + if (c->command_type != CLI_BUILDMODE_COMMAND) continue; + if (!strcmp(c->command, optarg_pair->name)) { + char *tmphelp; + if (asprintf(&tmphelp, "unset %s", optarg_pair->name)>=0) { + cli_optarg_addhelp(cmd, "setting", optarg_pair->name, tmphelp); + free_z(tmphelp); + } + } + } + } + } + } +} + int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char *argv[], int argc) { int rc = CLI_BUILDMODE_EXTEND; @@ -2491,6 +2534,7 @@ int cli_int_buildmode_cmd_cback(struct cli_def *cli, const char *command, char * cli_error(cli, "Extra arguments on command line, command ignored."); rc = CLI_ERROR; } + cli_int_buildmode_reset_unset_help(cli); return rc; } @@ -2506,6 +2550,7 @@ int cli_int_buildmode_flag_cback(struct cli_def *cli, const char *command, char cli_error(cli, "Problem setting value for optional flag %s", command); rc = CLI_ERROR; } + cli_int_buildmode_reset_unset_help(cli); return rc; } @@ -2522,6 +2567,7 @@ int cli_int_buildmode_flag_multiple_cback(struct cli_def *cli, const char *comma rc = CLI_ERROR; } + cli_int_buildmode_reset_unset_help(cli); return rc; } @@ -2547,16 +2593,15 @@ int cli_int_buildmode_execute_cback(struct cli_def *cli, const char *command, ch 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->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; - } + + 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->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; } } } @@ -2567,6 +2612,11 @@ int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char // Iterate over our 'set' variables to see if that variable is also a 'valid' command right now struct cli_command *c; + // have to catch this one here due to how buildmode works + if (!argv[0] || !*argv[0]) { + cli_error(cli, "Incomplete command, missing required argument 'setting' for command 'unset'"); + return CLI_ERROR; + } // 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; @@ -2577,6 +2627,7 @@ int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char // Go fry anything by this name cli_int_unset_optarg_value(cli, argv[0]); + cli_int_buildmode_reset_unset_help(cli); break; } @@ -2586,11 +2637,39 @@ int cli_int_buildmode_unset_cback(struct cli_def *cli, const char *command, char // 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) { + struct cli_optarg_pair *optarg_pair; + + for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { + // Only complete vars that could be set by current '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)) && (!word || !strncmp(word, optarg_pair->name, strlen(word)))) { + cli_add_comphelp_entry(comphelp, optarg_pair->name); + } + } + } return CLI_OK; } int cli_int_buildmode_unset_validator(struct cli_def *cli, const char *name, const char *value) { - return CLI_OK; + struct cli_optarg_pair *optarg_pair; + + if (!name || !*name) { + cli_error(cli, "No setting given to unset"); + return CLI_ERROR; + } + for (optarg_pair = cli->found_optargs; optarg_pair; optarg_pair = optarg_pair->next) { + // Only complete vars that could be set by current '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) && value && !strcmp(optarg_pair->name, value)) { + return CLI_OK; + } + } + } + return CLI_ERROR; } void cli_set_transient_mode(struct cli_def *cli, int transient_mode) { @@ -3284,7 +3363,7 @@ static void cli_int_parse_optargs(struct cli_def *cli, struct cli_pipeline_stage 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); + cli_error(cli, "Incomplete command, missing required argument '%s' for command '%s'", optarg->name, cmd->command); stage->status = CLI_MISSING_ARGUMENT; goto done; } From 543cdf09f630220cbda3b4c972018590898ffa7d Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 6 Sep 2019 15:44:11 -0400 Subject: [PATCH 18/21] Updated spec file --- libcli.spec | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libcli.spec b/libcli.spec index 09805f4..3aef762 100644 --- a/libcli.spec +++ b/libcli.spec @@ -67,6 +67,17 @@ rm -rf $RPM_BUILD_ROOT %defattr(-, root, root) %changelog +* Fri Sep 7 2019 Rob Sanders 1.10.2-1 +- Fix bug where 'extra help' added with cli_optarg_addhelp() were not + being displayed for optional argumenets +- Fix bug if 'unset' called in buildmode w/o an argument +- Added completor/validator logic for buildmode 'unset' command +- Fixed bug in how help was being created in buildmode that resulted + in badly formatted lines +- Add support so the buildmode unset command has dynamic help messages + depending on what has already been set +- Prevent spot check optargs from appearing in buildmode + * Tue Sep 3 2019 Rob Sanders 1.10.2-1 - Fix bug in cli_optarg_addhelp() - Change 'enter' to 'type' for buildmode commands autogenerated help From 1814352ab3078bdc0209bbca80d718e036f741db Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Fri, 6 Sep 2019 15:52:59 -0400 Subject: [PATCH 19/21] Uncomment idle timeout in clitest --- clitest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clitest.c b/clitest.c index ed9c48f..7f23acf 100644 --- a/clitest.c +++ b/clitest.c @@ -344,7 +344,7 @@ void run_child(int x) { 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); From 148080ee373a2570673cb6f71da13984b15768e7 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Thu, 12 Sep 2019 10:01:09 -0400 Subject: [PATCH 20/21] Fix issue where cli_option_addhelp() addds help to wrong option - requires API change to cli_register_optarg() and cli_optarg_addhelp() Problem is that an optarg can be added multiple times but with different modes, but the addhelp only search for the first occurrance of this optarg and added all help features there. Fix is to change the apis for cli_register_optarg() and cli_option_addhelp() - cli_register_optarg() now returns a 'cli_optarg *' instead of an int - cli_optarg_addhelp() now called with (struct cli_optarg *, const char *helpname, const char *helptext) --- clitest.c | 37 +++++++++++++++++++------------------ libcli.c | 19 +++++++++---------- libcli.h | 4 ++-- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/clitest.c b/clitest.c index 7f23acf..262f76e 100644 --- a/clitest.c +++ b/clitest.c @@ -327,6 +327,7 @@ int check1_validator(struct cli_def *cli, UNUSED(const char *name), UNUSED(const void run_child(int x) { struct cli_command *c; struct cli_def *cli; + struct cli_optarg *o; // Prepare a small user context char mymessage[] = "I contain user data!"; @@ -372,29 +373,29 @@ 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 verbose flagwith some humongously long string \nwithout any embedded newlines in it to test with", NULL, NULL, NULL); - cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", + o = cli_register_optarg(c, "color", CLI_CMD_OPTIONAL_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set color", color_completor, color_validator, NULL); - cli_optarg_addhelp(c, "color", "black" , "the color 'black'"); - cli_optarg_addhelp(c, "color", "white" , "the color 'white'"); - cli_optarg_addhelp(c, "color", "gray" , "the color 'gray'"); - cli_optarg_addhelp(c, "color", "red" , "the color 'red'"); - cli_optarg_addhelp(c, "color", "blue" , "the color 'blue'"); - cli_optarg_addhelp(c, "color", "green" , "the color 'green'"); - cli_optarg_addhelp(c, "color", "lightred" , "the color 'lightred'"); - cli_optarg_addhelp(c, "color", "lightblue" , "the color 'lightblue'"); - cli_optarg_addhelp(c, "color", "lightgreen" , "the color 'lightgreen'"); - cli_optarg_addhelp(c, "color", "darkred" , "the color 'darkred'"); - cli_optarg_addhelp(c, "color", "darkblue" , "the color 'darkblue'"); - cli_optarg_addhelp(c, "color", "darkgreen" , "the color 'darkgreen'"); - cli_optarg_addhelp(c, "color", "lavender" , "the color 'lavender'"); - cli_optarg_addhelp(c, "color", "yellow" , "the color 'yellow'"); + cli_optarg_addhelp(o, "black" , "the color 'black'"); + cli_optarg_addhelp(o, "white" , "the color 'white'"); + cli_optarg_addhelp(o, "gray" , "the color 'gray'"); + cli_optarg_addhelp(o, "red" , "the color 'red'"); + cli_optarg_addhelp(o, "blue" , "the color 'blue'"); + cli_optarg_addhelp(o, "green" , "the color 'green'"); + cli_optarg_addhelp(o, "lightred" , "the color 'lightred'"); + cli_optarg_addhelp(o, "lightblue" , "the color 'lightblue'"); + cli_optarg_addhelp(o, "lightgreen" , "the color 'lightgreen'"); + cli_optarg_addhelp(o, "darkred" , "the color 'darkred'"); + cli_optarg_addhelp(o, "darkblue" , "the color 'darkblue'"); + cli_optarg_addhelp(o, "darkgreen" , "the color 'darkgreen'"); + cli_optarg_addhelp(o, "lavender" , "the color 'lavender'"); + cli_optarg_addhelp(o, "yellow" , "the color 'yellow'"); 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, + o = cli_register_optarg(c, "shape", CLI_CMD_ARGUMENT | CLI_CMD_ALLOW_BUILDMODE, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Specify shape(shows subtext on help)", shape_completor, shape_validator, shape_transient_eval); - cli_optarg_addhelp(c, "shape", "triangle", "specify a triangle"); - cli_optarg_addhelp(c, "shape", "rectangle", "pecify a rectangle"); + cli_optarg_addhelp(o, "triangle", "specify a triangle"); + cli_optarg_addhelp(o, "rectangle", "specify a rectangle"); cli_register_optarg(c, "side_1", CLI_CMD_ARGUMENT, PRIVILEGE_UNPRIVILEGED, MODE_POLYGON_TRIANGLE, "Specify side 1 length", NULL, side_length_validator, NULL); diff --git a/libcli.c b/libcli.c index 1b395d1..3ceeb5f 100644 --- a/libcli.c +++ b/libcli.c @@ -2122,11 +2122,8 @@ void cli_free_optarg(struct cli_optarg *optarg) { free_z(optarg); } -int cli_optarg_addhelp(struct cli_command *cmd, const char *optargname, const char *helpname, const char *helptext) { +int cli_optarg_addhelp(struct cli_optarg *optarg, const char *helpname, const char *helptext) { char *tstr; - struct cli_optarg *optarg; - - for (optarg = cmd->optargs; optarg && strcmp(optarg->name, optargname); optarg = optarg->next) ; // put a vertical tab (\v), the new helpname, a horizontal tab (\t), and then the new help text if ((!optarg) || (asprintf(&tstr,"%s\v%s\t%s" , optarg->help, helpname, helptext) == -1)) { @@ -2139,19 +2136,19 @@ int cli_optarg_addhelp(struct cli_command *cmd, const char *optargname, const ch } -int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, int privilege, int mode, const char *help, +struct cli_optarg *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 *optarg = 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) { if (!strcmp(name, ptr->name) && ptr->mode == mode && ptr->privilege == privilege) { - return CLI_ERROR; + goto CLEANUP; } } if (!(optarg = calloc(sizeof(struct cli_optarg), 1))) goto CLEANUP; @@ -2175,8 +2172,9 @@ int cli_register_optarg(struct cli_command *cmd, const char *name, int flags, in CLEANUP: if (retval != CLI_OK) { cli_free_optarg(optarg); + optarg = NULL; } - return retval; + return optarg; } int cli_unregister_optarg(struct cli_command *cmd, const char *name) { @@ -2509,6 +2507,7 @@ void cli_int_buildmode_reset_unset_help(struct cli_def *cli) { * cli_optarg_addhelp() calls a few lines down */ if ((endOfMainHelp = strchr(optarg->help,'\v'))) *endOfMainHelp = '\0'; + 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->commands; @@ -2517,7 +2516,7 @@ void cli_int_buildmode_reset_unset_help(struct cli_def *cli) { if (!strcmp(c->command, optarg_pair->name)) { char *tmphelp; if (asprintf(&tmphelp, "unset %s", optarg_pair->name)>=0) { - cli_optarg_addhelp(cmd, "setting", optarg_pair->name, tmphelp); + cli_optarg_addhelp(optarg, optarg_pair->name, tmphelp); free_z(tmphelp); } } diff --git a/libcli.h b/libcli.h index 136b6f0..e2122b8 100644 --- a/libcli.h +++ b/libcli.h @@ -234,12 +234,12 @@ struct cli_command *cli_register_filter(struct cli_def *cli, const char *command 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, +struct cli_optarg *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 *)); -int cli_optarg_addhelp(struct cli_command *cmd, const char *optargname, const char *helpname, const char *helptext); +int cli_optarg_addhelp(struct cli_optarg *optarg, const char *helpname, const char *helptext); 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); From 15e5d526c5187acd338d1d56fbe64459ef1005f9 Mon Sep 17 00:00:00 2001 From: Rob Sanders Date: Tue, 24 Sep 2019 07:58:22 -0400 Subject: [PATCH 21/21] Update spec file with breaking API change required to fix bug in 1.10.0 - In order to have cli_optarg_addhelp() add the extra help text to the right optarg, the return value of cli_register_optarg() was changed from an int to a 'struct optarg *' --- libcli.spec | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libcli.spec b/libcli.spec index 3aef762..2891d30 100644 --- a/libcli.spec +++ b/libcli.spec @@ -77,6 +77,11 @@ rm -rf $RPM_BUILD_ROOT - Add support so the buildmode unset command has dynamic help messages depending on what has already been set - Prevent spot check optargs from appearing in buildmode +- BREAKING API CHANGE - the 'cli_register_optarg()' function is now returning + a pointer to the newly added optarg or NULL instead of an integer + return code. This is to fix a design bug where it was difficult to + use the new 'cli_optarg_addhelp()' function. Only affects moving from + 1.10.0 to 1.10.2 (1.10.1 was never released) * Tue Sep 3 2019 Rob Sanders 1.10.2-1 - Fix bug in cli_optarg_addhelp()