diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd99b1a..bcef7c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,10 @@ Expected: June 2021 ### Minor features * Add default network namespace constant: `RESTCONF_NETNS_DEFAULT` with default value "default". - +* CLI: Two new hide variables added (thanks: shmuelnatan) + * hide-database : specifies that a command is not visible in database. This can be useful for setting passwords and not exposing them to users. + * hide-database-auto-completion : specifies that a command is not visible in database and in auto completion. This can be useful for a password that was put in device by super user, not be changed. + ## 5.1.0 15 April 2021 diff --git a/apps/cli/cli_auto.c b/apps/cli/cli_auto.c index 0441937e..a76e38d4 100644 --- a/apps/cli/cli_auto.c +++ b/apps/cli/cli_auto.c @@ -120,6 +120,315 @@ cvec_append(cvec *cvv0, return cvv2; } +/*! x is element and has exactly one child which in turn has none + * @see child_type in clixon_json.c + */ +static int +tleaf(cxobj *x) +{ + cxobj *xc; + + if (xml_type(x) != CX_ELMNT) + return 0; + if (xml_child_nr_notype(x, CX_ATTR) != 1) + return 0; + /* From here exactly one noattr child, get it */ + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + if (xml_type(xc) != CX_ATTR) + break; + if (xc == NULL) + return -1; /* n/a */ + return (xml_child_nr_notype(xc, CX_ATTR) == 0); +} + +/*! Print an XML tree structure to an output stream and encode chars "<>&" + * + * @param[in] xn clicon xml tree + * @param[in] level how many spaces to insert before each line + * @param[in] prettyprint insert \n and spaces tomake the xml more readable. + * @param[in] fn Callback to make print function + * @see clicon_xml2cbuf + * One can use clicon_xml2cbuf to get common code, but using fprintf is + * much faster than using cbuf and then printing that,... + */ +int +cli_xml2file(cxobj *xn, + int level, + int prettyprint, + clicon_output_cb *fn) +{ + int retval = -1; + char *name; + char *namespace; + cxobj *xc; + int hasbody; + int haselement; + char *val; + char *encstr = NULL; /* xml encoded string */ + char *opext = NULL; + + if (xn == NULL) + goto ok; + /* Look for autocli-op defined in clixon-lib.yang */ + if (yang_extension_value(xml_spec(xn), "autocli-op", CLIXON_LIB_NS, &opext) < 0) { + goto ok; + } + if ((opext != NULL) && ((strcmp(opext, "hide-database") == 0) || (strcmp(opext, "hide-database-auto-completion") == 0))){ + goto ok; + } + name = xml_name(xn); + namespace = xml_prefix(xn); + switch(xml_type(xn)){ + case CX_BODY: + if ((val = xml_value(xn)) == NULL) /* incomplete tree */ + break; + if (xml_chardata_encode(&encstr, "%s", val) < 0) + goto done; + (*fn)(stdout, "%s", encstr); + break; + case CX_ATTR: + (*fn)(stdout, " "); + if (namespace) + (*fn)(stdout, "%s:", namespace); + (*fn)(stdout, "%s=\"%s\"", name, xml_value(xn)); + break; + case CX_ELMNT: + (*fn)(stdout, "%*s<", prettyprint?(level*3):0, ""); + if (namespace) + (*fn)(stdout, "%s:", namespace); + (*fn)(stdout, "%s", name); + hasbody = 0; + haselement = 0; + xc = NULL; + /* print attributes only */ + while ((xc = xml_child_each(xn, xc, -1)) != NULL) { + switch (xml_type(xc)){ + case CX_ATTR: + if (cli_xml2file(xc, level+1, prettyprint, fn) <0) + goto done; + break; + case CX_BODY: + hasbody=1; + break; + case CX_ELMNT: + haselement=1; + break; + default: + break; + } + } + /* Check for special case instead of : + * Ie, no CX_BODY or CX_ELMNT child. + */ + if (hasbody==0 && haselement==0) + (*fn)(stdout, "/>"); + else{ + (*fn)(stdout, ">"); + if (prettyprint && hasbody == 0) + (*fn)(stdout, "\n"); + xc = NULL; + while ((xc = xml_child_each(xn, xc, -1)) != NULL) { + if (xml_type(xc) != CX_ATTR) + if (cli_xml2file(xc, level+1, prettyprint, fn) <0) + goto done; + } + if (prettyprint && hasbody==0) + (*fn)(stdout, "%*s", level*3, ""); + (*fn)(stdout, "", name); + } + if (prettyprint) + (*fn)(stdout, "\n"); + break; + default: + break; + }/* switch */ + ok: + retval = 0; + done: + if (encstr) + free(encstr); + return retval; +} + +/*! Translate XML to a "pseudo-code" textual format using a callback - internal function + * @param[in] xn XML object to print + * @param[in] fn Callback to make print function + * @param[in] level print 4 spaces per level in front of each line + */ +int +cli_xml2txt(cxobj *xn, + clicon_output_cb *fn, + int level) +{ + cxobj *xc = NULL; + int children=0; + int retval = -1; + char *opext = NULL; + + if (xn == NULL || fn == NULL){ + clicon_err(OE_XML, EINVAL, "xn or fn is NULL"); + goto done; + } + /* Look for autocli-op defined in clixon-lib.yang */ + if (yang_extension_value(xml_spec(xn), "autocli-op", CLIXON_LIB_NS, &opext) < 0) { + goto ok; + } + if ((opext != NULL) && ((strcmp(opext, "hide-database") == 0) || (strcmp(opext, "hide-database-auto-completion") == 0))){ + goto ok; + } + xc = NULL; /* count children (elements and bodies, not attributes) */ + while ((xc = xml_child_each(xn, xc, -1)) != NULL) + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + children++; + if (!children){ /* If no children print line */ + switch (xml_type(xn)){ + case CX_BODY: + (*fn)(stdout, "%s;\n", xml_value(xn)); + break; + case CX_ELMNT: + (*fn)(stdout, "%*s%s;\n", 4*level, "", xml_name(xn)); + break; + default: + break; + } + goto ok; + } + (*fn)(stdout, "%*s", 4*level, ""); + (*fn)(stdout, "%s ", xml_name(xn)); + if (!tleaf(xn)) + (*fn)(stdout, "{\n"); + xc = NULL; + while ((xc = xml_child_each(xn, xc, -1)) != NULL){ + if (xml_type(xc) == CX_ELMNT || xml_type(xc) == CX_BODY) + if (cli_xml2txt(xc, fn, level+1) < 0) + break; + } + if (!tleaf(xn)) + (*fn)(stdout, "%*s}\n", 4*level, ""); + ok: + retval = 0; + done: + return retval; +} + +/*! Translate from XML to CLI commands + * Howto: join strings and pass them down. + * Identify unique/index keywords for correct set syntax. + * @param[in] xn XML Parse-tree (to translate) + * @param[in] prepend Print this text in front of all commands. + * @param[in] gt option to steer cli syntax + * @param[in] fn Callback to make print function + */ +int +cli_xml2cli(cxobj *xn, + char *prepend, + enum genmodel_type gt, + clicon_output_cb *fn) +{ + int retval = -1; + cxobj *xe = NULL; + cbuf *cbpre = NULL; + yang_stmt *ys; + int match; + char *body; + char *opext = NULL; + + if (xml_type(xn)==CX_ATTR) + goto ok; + if ((ys = xml_spec(xn)) == NULL) + goto ok; + /* Look for autocli-op defined in clixon-lib.yang */ + if (yang_extension_value(xml_spec(xn), "autocli-op", CLIXON_LIB_NS, &opext) < 0) { + goto ok; + } + if ((opext != NULL) && ((strcmp(opext, "hide-database") == 0) || (strcmp(opext, "hide-database-auto-completion") == 0))){ + goto ok; + } + /* If leaf/leaf-list or presence container, then print line */ + if (yang_keyword_get(ys) == Y_LEAF || + yang_keyword_get(ys) == Y_LEAF_LIST){ + if (prepend) + (*fn)(stdout, "%s", prepend); + if (gt == GT_ALL || gt == GT_VARS || gt == GT_HIDE) + (*fn)(stdout, "%s ", xml_name(xn)); + if ((body = xml_body(xn)) != NULL){ + if (index(body, ' ')) + (*fn)(stdout, "\"%s\"", body); + else + (*fn)(stdout, "%s", body); + } + (*fn)(stdout, "\n"); + goto ok; + } + /* Create prepend variable string */ + if ((cbpre = cbuf_new()) == NULL){ + clicon_err(OE_PLUGIN, errno, "cbuf_new"); + goto done; + } + if (prepend) + cprintf(cbpre, "%s", prepend); + + /* If non-presence container && HIDE mode && only child is + * a list, then skip container keyword + * See also yang2cli_container */ + if (yang_container_cli_hide(ys, gt) == 0) + cprintf(cbpre, "%s ", xml_name(xn)); + + /* If list then first loop through keys */ + if (yang_keyword_get(ys) == Y_LIST){ + xe = NULL; + while ((xe = xml_child_each(xn, xe, -1)) != NULL){ + if ((match = yang_key_match(ys, xml_name(xe))) < 0) + goto done; + if (!match) + continue; + if (gt == GT_ALL) + cprintf(cbpre, "%s ", xml_name(xe)); + cprintf(cbpre, "%s ", xml_body(xe)); + } + } + else if ((yang_keyword_get(ys) == Y_CONTAINER) && + yang_find(ys, Y_PRESENCE, NULL) != NULL){ + /* If presence container, then print as leaf (but continue to children) */ + if (prepend) + (*fn)(stdout, "%s", prepend); + if (gt == GT_ALL || gt == GT_VARS || gt == GT_HIDE) + (*fn)(stdout, "%s ", xml_name(xn)); + if ((body = xml_body(xn)) != NULL){ + if (index(body, ' ')) + (*fn)(stdout, "\"%s\"", body); + else + (*fn)(stdout, "%s", body); + } + (*fn)(stdout, "\n"); + } + + /* Then loop through all other (non-keys) */ + xe = NULL; + while ((xe = xml_child_each(xn, xe, -1)) != NULL){ + if (yang_keyword_get(ys) == Y_LIST){ + if ((match = yang_key_match(ys, xml_name(xe))) < 0) + goto done; + if (match){ + (*fn)(stdout, "%s\n", cbuf_get(cbpre)); + continue; /* Not key itself */ + } + } + if (cli_xml2cli(xe, cbuf_get(cbpre), gt, fn) < 0) + goto done; + } + ok: + retval = 0; + done: + if (cbpre) + cbuf_free(cbpre); + return retval; +} + /*! Enter a CLI edit mode * @param[in] h CLICON handle * @param[in] cvv Vector of variables from CLIgen command-line @@ -413,10 +722,10 @@ cli_auto_show(clicon_handle h, switch (format){ case FORMAT_XML: if (isroot) - clicon_xml2file(stdout, xp, 0, pretty); + cli_xml2file(xp, 0, pretty, fprintf); else{ while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) - clicon_xml2file(stdout, xc, 0, pretty); + cli_xml2file(xc, 0, pretty, fprintf); } fprintf(stdout, "\n"); break; @@ -431,19 +740,19 @@ cli_auto_show(clicon_handle h, break; case FORMAT_TEXT: if (isroot) - xml2txt_cb(stdout, xp, cligen_output); /* tree-formed text */ + cli_xml2txt(xp, cligen_output, 0); /* tree-formed text */ else while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) - xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */ + cli_xml2txt(xc, cligen_output, 0); /* tree-formed text */ break; case FORMAT_CLI: if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) goto done; if (isroot) - xml2cli_cb(stdout, xp, prefix, gt, cligen_output); /* cli syntax */ + cli_xml2cli(xp, prefix, gt, cligen_output); /* cli syntax */ else while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) - xml2cli_cb(stdout, xc, prefix, gt, cligen_output); /* cli syntax */ + cli_xml2cli(xc, prefix, gt, cligen_output); /* cli syntax */ break; case FORMAT_NETCONF: fprintf(stdout, "", @@ -451,10 +760,10 @@ cli_auto_show(clicon_handle h, if (pretty) fprintf(stdout, "\n"); if (isroot) - clicon_xml2file(stdout, xp, 2, pretty); + cli_xml2file(xp, 2, pretty, fprintf); else while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) - clicon_xml2file(stdout, xc, 2, pretty); + cli_xml2file(xc, 2, pretty, fprintf); fprintf(stdout, "]]>]]>\n"); break; } /* switch */ diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index bf76b26f..6e7b8f5e 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -119,7 +119,14 @@ cli_expand_var_generate(clicon_handle h, cbuf *cb) { int retval = -1; - char *api_path_fmt = NULL; + char *api_path_fmt = NULL, *opext = NULL; + + if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, &opext) < 0) + goto done; + if (opext && strcmp(opext, "hide-database") == 0) { + retval = 1; + goto done; + } if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0) goto done; @@ -574,7 +581,7 @@ yang2cli_var(clicon_handle h, uint8_t fraction_digits = 0; enum cv_type cvtype; int options = 0; - int completionp; + int completionp, result; char *type; if ((patterns = cvec_new(0)) == NULL){ @@ -599,10 +606,12 @@ yang2cli_var(clicon_handle h, if (yang2cli_var_union(h, ys, origtype, yrestype, helptext, cb) < 0) goto done; if (clicon_cli_genmodel_completion(h)){ - if (cli_expand_var_generate(h, ys, cvtype, + result = cli_expand_var_generate(h, ys, cvtype, options, fraction_digits, - cb) < 0) + cb); + if (result < 0) goto done; + if (result == 0) yang2cli_helptext(cb, helptext); } cprintf(cb, ")"); @@ -622,10 +631,12 @@ yang2cli_var(clicon_handle h, options, cvv, patterns, fraction_digits, cb)) < 0) goto done; if (completionp){ - if (cli_expand_var_generate(h, ys, cvtype, + result = cli_expand_var_generate(h, ys, cvtype, options, fraction_digits, - cb) < 0) + cb); + if (result < 0) goto done; + if (result == 0) yang2cli_helptext(cb, helptext); cprintf(cb, ")"); } @@ -688,6 +699,10 @@ yang2cli_leaf(clicon_handle h, cprintf(cb, ",hide{"); extralevel = 1; } + if (opext && strcmp(opext, "hide-database-auto-completion") == 0){ + cprintf(cb, ",hide-database-auto-completion{"); + extralevel = 1; + } if (yang2cli_var(h, ys, helptext, cb) < 0) goto done; } @@ -695,6 +710,9 @@ yang2cli_leaf(clicon_handle h, if (opext && strcmp(opext, "hide") == 0){ cprintf(cb, ",hide"); } + if (opext && strcmp(opext, "hide-database-auto-completion") == 0){ + cprintf(cb, ",hide-database-auto-completion"); + } } } else{ @@ -767,6 +785,9 @@ yang2cli_container(clicon_handle h, if (opext != NULL && strcmp(opext, "hide") == 0){ cprintf(cb, ",hide"); } + if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){ + cprintf(cb, ",hide-database-auto-completion"); + } cprintf(cb, ";{\n"); } @@ -815,13 +836,6 @@ yang2cli_list(clicon_handle h, char *opext = NULL; cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys)); - /* Look for autocli-op defined in clixon-lib.yang */ - if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, &opext) < 0) - goto done; - if (opext != NULL && strcmp(opext, "hide") == 0){ - cprintf(cb, ",hide"); - extralevel = 1; - } if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){ if ((helptext = strdup(yang_argument_get(yd))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -831,6 +845,17 @@ yang2cli_list(clicon_handle h, *s = '\0'; yang2cli_helptext(cb, helptext); } + /* Look for autocli-op defined in clixon-lib.yang */ + if (yang_extension_value(ys, "autocli-op", CLIXON_LIB_NS, &opext) < 0) + goto done; + if (opext != NULL && strcmp(opext, "hide") == 0){ + cprintf(cb, ",hide"); + extralevel = 1; + } + if (opext != NULL && strcmp(opext, "hide-database-auto-completion") == 0){ + cprintf(cb, ",hide-database-auto-completion"); + extralevel = 1; + } /* Loop over all key variables */ cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */ cvi = NULL; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index e323d8b3..28947ea4 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -476,7 +476,7 @@ cli_show_config1(clicon_handle h, case FORMAT_XML: xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, -1)) != NULL) - clicon_xml2file_cb(stdout, xc, 0, 1, cligen_output); + cli_xml2file(xc, 0, 1, cligen_output); break; case FORMAT_JSON: xml2json_cb(stdout, xt, 1, cligen_output); @@ -484,7 +484,7 @@ cli_show_config1(clicon_handle h, case FORMAT_TEXT: xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, -1)) != NULL) - xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */ + cli_xml2txt(xc, cligen_output, 0); /* tree-formed text */ break; case FORMAT_CLI: /* get CLI generatade mode: VARS|ALL */ @@ -492,14 +492,14 @@ cli_show_config1(clicon_handle h, goto done; xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) - xml2cli_cb(stdout, xc, prefix, gt, cligen_output); /* cli syntax */ + cli_xml2cli(xc, prefix, gt, cligen_output); /* cli syntax */ break; case FORMAT_NETCONF: cligen_output(stdout, "\n", NETCONF_BASE_NAMESPACE); xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, -1)) != NULL) - clicon_xml2file_cb(stdout, xc, 2, 1, cligen_output); + cli_xml2file(xc, 2, 1, cligen_output); cligen_output(stdout, "]]>]]>\n"); break; } @@ -623,7 +623,7 @@ show_conf_xpath(clicon_handle h, if (xpath_vec(xt, nsc, "%s", &xv, &xlen, xpath) < 0) goto done; for (i=0; i\n"); - clicon_xml2file(stdout, xp, 2, 1); + cli_xml2file(xp, 2, 1, fprintf); fprintf(stdout, "]]>]]>\n"); break; default: for (; i < xml_child_nr(xml_parent(xp)) ; ++i, xp_helper = xml_child_i(xml_parent(xp), i)) { switch (format){ case FORMAT_XML: - clicon_xml2file(stdout, xp_helper, 0, 1); + cli_xml2file(xp_helper, 0, 1, fprintf); break; case FORMAT_JSON: xml2json_cb(stdout, xp_helper, 1, cligen_output); break; case FORMAT_TEXT: - xml2txt_cb(stdout, xp_helper, cligen_output); /* tree-formed text */ + cli_xml2txt(xp_helper, cligen_output, 0); /* tree-formed text */ break; default: /* see cli_show_config() */ break; diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 2a5d4a60..457c4840 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -116,6 +116,9 @@ int cli_help(clicon_handle h, cvec *vars, cvec *argv); /* In cli_show.c */ int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, cvec *commands, cvec *helptexts); +int cli_xml2file (cxobj *xn, int level, int prettyprint, clicon_output_cb *fn); +int cli_xml2txt(cxobj *xn, clicon_output_cb *fn, int level); +int cli_xml2cli(cxobj *xn, char *prepend, enum genmodel_type gt, clicon_output_cb *fn); /* cli_show.c: CLIgen new vector arg callbacks */ int show_yang(clicon_handle h, cvec *vars, cvec *argv); diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index caac71e6..1d1f1b25 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -171,13 +171,13 @@ static int flogtime(FILE *f) { struct timeval tv; - struct tm tm; + struct tm *tm; gettimeofday(&tv, NULL); - localtime_r((time_t*)&tv.tv_sec, &tm); + tm = localtime((time_t*)&tv.tv_sec); fprintf(f, "%s %2d %02d:%02d:%02d: ", - mon2name(tm.tm_mon), tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec); + mon2name(tm->tm_mon), tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); return 0; } diff --git a/lib/src/clixon_xml_io.c b/lib/src/clixon_xml_io.c index 7b2e1a2f..d51bdc60 100644 --- a/lib/src/clixon_xml_io.c +++ b/lib/src/clixon_xml_io.c @@ -113,7 +113,7 @@ xml2file_recurse(FILE *f, int haselement; char *val; char *encstr = NULL; /* xml encoded string */ - + if (x == NULL) goto ok; name = xml_name(x); diff --git a/yang/clixon/clixon-lib@2021-03-08.yang b/yang/clixon/clixon-lib@2021-03-08.yang index 44c5c0c8..4923793a 100644 --- a/yang/clixon/clixon-lib@2021-03-08.yang +++ b/yang/clixon/clixon-lib@2021-03-08.yang @@ -103,7 +103,9 @@ module clixon-lib { this point in the YANG tree for the automated generated CLI. Note that this extension is only used in clixon_cli. Operations is expected to be extended, but the following operations are defined: - - hide This command is active but not shown by ? or TAB"; + - hide This command is active but not shown by ? or TAB (meaning, it hides the auto-completion of commands) + - hide-database This command hides the database + - hide-database-auto-completion This command hides the database and the auto completion (meaning, this command acts as both commands above)"; argument cliop; } rpc debug {