/* -*- mode: c; c-file-style: "openbsd" -*- */ /* * Copyright (c) 2012 Vincent Bernat * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "client.h" #include #include #include #include #include #include #include #include /** * An element of the environment (a key and a value). */ struct cmd_env_el { TAILQ_ENTRY(cmd_env_el) next; /**< Next environment element */ const char *key; /**< Key for this element */ const char *value; /**< Value for this element */ }; /** * A stack element. */ struct cmd_env_stack { TAILQ_ENTRY(cmd_env_stack) next; /**< Next element, down the stack */ struct cmd_node *el; /**< Stored element */ }; /** * Structure representing an environment for the current command. * * An environment is a list of values stored for use for the function executing * as well as the current command, the current position in the command and a * stack for cmd_node */ struct cmd_env { TAILQ_HEAD(, cmd_env_el) elements; /**< List of environment variables */ TAILQ_HEAD(, cmd_env_stack) stack; /**< Stack */ int argc; /**< Number of argument in the command */ int argp; /**< Current argument */ const char **argv; /**< Arguments */ }; /** * Structure representing a command node. * * Such a node contains a token accepted to enter the node (or @c NULL if there * is no token needed), a documentation string to present the user, a function * to validate the user input (or @c NULL if no function is needed) and a * function to execute when entering the node. Because we can enter a node just * by completing, the execution part should have no other effect than modifying * the environment, with the exception of execution on @c NEWLINE (which cannot * happen when completing). */ struct cmd_node { TAILQ_ENTRY(cmd_node) next; /**< Next sibling */ const char *token; /**< Token to enter this cnode */ const char *doc; /**< Documentation string */ int privileged; /**< Privileged command? */ int lock; /**< Lock required for execution? */ int hidden; /**< Hidden command? */ /** * Function validating entry in this node. Can be @c NULL. */ int (*validate)(struct cmd_env *, const void *); /** * Function to execute when entering this node. May be @c NULL. * * This function can alter the environment */ int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *, const void *); const void *arg; /**< Magic argument for the previous two functions */ /* List of possible subentries */ TAILQ_HEAD(, cmd_node) subentries; /* List of subnodes */ }; /** * Create a root node. * * @return the root node. */ struct cmd_node * commands_root(void) { struct cmd_node *new = calloc(1, sizeof(struct cmd_node)); if (new == NULL) fatalx("lldpctl", "out of memory"); TAILQ_INIT(&new->subentries); return new; } /** * Make a node accessible only to privileged users. * * @param node node to change privileges * @return the modified node * * The node is modified. It is returned to ease chaining. */ struct cmd_node * commands_privileged(struct cmd_node *node) { if (node) node->privileged = 1; return node; } /** * Make a node accessible only with a lock. * * @param node node to use lock to execute * @return the modified node * * The node is modified. It is returned to ease chaining. */ struct cmd_node * commands_lock(struct cmd_node *node) { if (node) node->lock = 1; return node; } /** * Hide a node from help or completion. * * @param node node to hide * @return the modified node * * The node is modified. It is returned to ease chaining. */ struct cmd_node * commands_hidden(struct cmd_node *node) { if (node) node->hidden = 1; return node; } /** * Create a new node acessible by any user. * * @param root The node we want to attach this node. * @param token Token to enter this node. Or @c NULL if no token is needed. * @param doc Documentation for this node. * @param validate Function that should return 1 if we can enter the node. * @param execute Function that should return 1 on successful execution of this node. * @param arg Magic argument for precedent functions. * @return the newly created node */ struct cmd_node * commands_new(struct cmd_node *root, const char *token, const char *doc, int (*validate)(struct cmd_env *, const void *), int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *, const void *), const void *arg) { struct cmd_node *new = calloc(1, sizeof(struct cmd_node)); if (new == NULL) fatalx("lldpctl", "out of memory"); new->token = token; new->doc = doc; new->validate = validate; new->execute = execute; new->arg = arg; TAILQ_INIT(&new->subentries); TAILQ_INSERT_TAIL(&root->subentries, new, next); return new; } /** * Free a command tree. * * @param root The node we want to free. */ void commands_free(struct cmd_node *root) { struct cmd_node *subcmd, *subcmd_next; for (subcmd = TAILQ_FIRST(&root->subentries); subcmd != NULL; subcmd = subcmd_next) { subcmd_next = TAILQ_NEXT(subcmd, next); TAILQ_REMOVE(&root->subentries, subcmd, next); commands_free(subcmd); } free(root); } /** * Return the current argument in the environment. This can be @c NEWLINE or * @c NULL. * * @param env The environment. * @return current argument. */ const char * cmdenv_arg(struct cmd_env *env) { if (env->argp < env->argc) return env->argv[env->argp]; if (env->argp == env->argc) return NEWLINE; return NULL; } /** * Get a value from the environment. * * @param env The environment. * @param key The key for the requested value. * @return @c NULL if not found or the requested value otherwise. If no value is * associated, return the key. */ const char * cmdenv_get(struct cmd_env *env, const char *key) { struct cmd_env_el *el; TAILQ_FOREACH (el, &env->elements, next) if (!strcmp(el->key, key)) return el->value ? el->value : el->key; return NULL; } /** * Put a value in the environment. * * @param env The environment. * @param key The key for the value. * @param value The value. * @return 0 on success, -1 otherwise. */ int cmdenv_put(struct cmd_env *env, const char *key, const char *value) { struct cmd_env_el *el = malloc(sizeof(struct cmd_env_el)); if (el == NULL) { log_warn("lldpctl", "unable to allocate memory for new environment variable"); return -1; } el->key = key; el->value = value; TAILQ_INSERT_TAIL(&env->elements, el, next); return 0; } /** * Pop some node from the execution stack. * * This allows to resume parsing on a previous state. Useful to call after * parsing optional arguments. * * @param env The environment. * @param n How many element we want to pop. * @return 0 on success, -1 otherwise. */ int cmdenv_pop(struct cmd_env *env, int n) { while (n-- > 0) { if (TAILQ_EMPTY(&env->stack)) { log_warnx("lldpctl", "environment stack is empty"); return -1; } struct cmd_env_stack *first = TAILQ_FIRST(&env->stack); TAILQ_REMOVE(&env->stack, first, next); free(first); } return 0; } /** * Push some node on the execution stack. * * @param env The environment. * @param node The node to push. * @return 0 on success, -1 on error. */ static int cmdenv_push(struct cmd_env *env, struct cmd_node *node) { struct cmd_env_stack *el = malloc(sizeof(struct cmd_env_stack)); if (el == NULL) { log_warn("lldpctl", "not enough memory to allocate a stack element"); return -1; } el->el = node; TAILQ_INSERT_HEAD(&env->stack, el, next); return 0; } /** * Return the top of the stack, without poping it. * * @param env The environment. * @return the top element or @c NULL is the stack is empty. */ static struct cmd_node * cmdenv_top(struct cmd_env *env) { if (TAILQ_EMPTY(&env->stack)) return NULL; return TAILQ_FIRST(&env->stack)->el; } /** * Free execution environment. * * @param env The environment. */ static void cmdenv_free(struct cmd_env *env) { while (!TAILQ_EMPTY(&env->stack)) cmdenv_pop(env, 1); struct cmd_env_el *first; while (!TAILQ_EMPTY(&env->elements)) { first = TAILQ_FIRST(&env->elements); TAILQ_REMOVE(&env->elements, first, next); free(first); } } struct candidate_word { TAILQ_ENTRY(candidate_word) next; const char *word; const char *doc; int hidden; }; /** * Execute or complete a command from the given node. * * @param conn Connection to lldpd. * @param w Writer for output. * @param root Root node we want to start from. * @param argc Number of arguments. * @param argv Array of arguments. * @param word Completed word. Or NULL when no completion is required. * @param all When completing, display possible completions even if only one choice * is possible. * @param priv Is the current user privileged? * @return 0 on success, -1 otherwise. */ static int _commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root, int argc, const char **argv, char **word, int all, int priv) { int n, rc = 0, completion = (word != NULL); int help = 0; /* Are we asking for help? */ int complete = 0; /* Are we asking for possible completions? */ int needlock = 0; /* Do we need a lock? */ struct cmd_env env = { .elements = TAILQ_HEAD_INITIALIZER(env.elements), .stack = TAILQ_HEAD_INITIALIZER(env.stack), .argc = argc, .argv = argv, .argp = 0 }; static int lockfd = -1; static char *lockname = NULL; /* Name of lockfile */ cmdenv_push(&env, root); if (!completion) for (n = 0; n < argc; n++) log_debug("lldpctl", "argument %02d: `%s`", n, argv[n]); if (completion) *word = NULL; #define CAN_EXECUTE(candidate) \ ((!candidate->privileged || priv || complete) && \ (!candidate->validate || candidate->validate(&env, candidate->arg) == 1)) /* When completion is in progress, we use the same algorithm than for * execution until we reach the cursor position. */ struct cmd_node *current = NULL; while ((current = cmdenv_top(&env))) { if (!completion) { help = !!cmdenv_get(&env, "help"); /* Are we asking for help? */ complete = !!cmdenv_get(&env, "complete"); /* Or completion? */ } struct cmd_node *candidate, *best = NULL; const char *token = (env.argp < env.argc) ? env.argv[env.argp] : (env.argp == env.argc && !help && !complete) ? NEWLINE : NULL; if (token == NULL || (completion && env.argp == env.argc - 1)) goto end; if (!completion) log_debug("lldpctl", "process argument %02d: `%s`", env.argp, token); TAILQ_FOREACH (candidate, ¤t->subentries, next) { if (candidate->token && !strncmp(candidate->token, token, strlen(token)) && CAN_EXECUTE(candidate)) { if (candidate->token && !strcmp(candidate->token, token)) { /* Exact match */ best = candidate; needlock = needlock || candidate->lock; break; } if (!best) best = candidate; else { if (!completion) log_warnx("lldpctl", "ambiguous token: %s (%s or %s)", token, candidate->token, best->token); rc = -1; goto end; } } } if (!best) { /* Take first that validate */ TAILQ_FOREACH (candidate, ¤t->subentries, next) { if (!candidate->token && CAN_EXECUTE(candidate)) { best = candidate; needlock = needlock || candidate->lock; break; } } } if (!best && env.argp == env.argc) goto end; if (!best) { if (!completion) log_warnx("lldpctl", "unknown command from argument %d: `%s`", env.argp + 1, token); rc = -1; goto end; } /* Push and execute */ cmdenv_push(&env, best); if (best->execute) { if (needlock) { if (lockfd == -1) { if (lockname == NULL && asprintf(&lockname, "%s.lock", ctlname) == -1) { log_warnx("lldpctl", "not enough memory to build lock filename"); rc = -1; goto end; } log_debug("lldpctl", "open %s for locking", lockname); if ((lockfd = open(lockname, O_RDWR)) == -1) { log_warn("lldpctl", "cannot open lock %s", lockname); rc = -1; goto end; } } if (lockf(lockfd, F_LOCK, 0) == -1) { log_warn("lldpctl", "cannot get lock on %s", lockname); rc = -1; close(lockfd); lockfd = -1; goto end; } } rc = best->execute(conn, w, &env, best->arg) != 1 ? -1 : rc; if (needlock && lockf(lockfd, F_ULOCK, 0) == -1) { log_warn("lldpctl", "cannot unlock %s", lockname); close(lockfd); lockfd = -1; } if (rc == -1) goto end; } env.argp++; } end: if (!completion && !help && !complete) { if (rc == 0 && env.argp != env.argc + 1) { log_warnx("lldpctl", "incomplete command"); rc = -1; } } else if (rc == 0 && (env.argp == env.argc - 1 || help || complete)) { /* We need to complete. Let's build the list of candidate words. */ struct cmd_node *candidate = NULL; size_t maxl = 10; /* Max length of a word */ TAILQ_HEAD(, candidate_word) words; /* List of subnodes */ TAILQ_INIT(&words); current = cmdenv_top(&env); if (!TAILQ_EMPTY(¤t->subentries)) { TAILQ_FOREACH (candidate, ¤t->subentries, next) { if ((!candidate->token || help || complete || !strncmp(env.argv[env.argc - 1], candidate->token, strlen(env.argv[env.argc - 1]))) && CAN_EXECUTE(candidate)) { struct candidate_word *cword = malloc(sizeof(struct candidate_word)); if (!cword) break; cword->word = candidate->token; cword->doc = candidate->doc; cword->hidden = candidate->hidden; if (cword->word && strlen(cword->word) > maxl) maxl = strlen(cword->word); TAILQ_INSERT_TAIL(&words, cword, next); } } } if (!TAILQ_EMPTY(&words)) { /* Search if there is a common prefix, then return it. */ char prefix[maxl + 2]; /* Extra space may be added at the end */ struct candidate_word *cword, *cword_next; memset(prefix, 0, maxl + 2); for (size_t n = 0; n < maxl; n++) { int c = 1; /* Set to 0 to exit outer loop */ TAILQ_FOREACH (cword, &words, next) { c = 0; if (cword->hidden) continue; if (cword->word == NULL) break; if (!strcmp(cword->word, NEWLINE)) break; if (strlen(cword->word) == n) break; if (prefix[n] == '\0') prefix[n] = cword->word[n]; else if (prefix[n] != cword->word[n]) break; c = 1; } if (c == 0) { prefix[n] = '\0'; break; } } /* If the prefix is complete, add a space, otherwise, * just return it as is. */ if (!all && !help && !complete && strcmp(prefix, NEWLINE) && strlen(prefix) > 0 && strlen(env.argv[env.argc - 1]) < strlen(prefix)) { TAILQ_FOREACH (cword, &words, next) { if (cword->word && !strcmp(prefix, cword->word)) { prefix[strlen(prefix)] = ' '; break; } } *word = strdup(prefix); } else { /* No common prefix, print possible completions */ if (!complete) fprintf(stderr, "\n-- \033[1;34m%s\033[0m\n", current->doc ? current->doc : "Help"); TAILQ_FOREACH (cword, &words, next) { if (cword->hidden) continue; char fmt[100]; if (!complete) { snprintf(fmt, sizeof(fmt), "%s%%%ds%s %%s\n", "\033[1m", (int)maxl, "\033[0m"); fprintf(stderr, fmt, cword->word ? cword->word : "WORD", cword->doc ? cword->doc : "..."); } else { if (!cword->word || !strcmp(cword->word, NEWLINE)) continue; fprintf(stdout, "%s %s\n", cword->word ? cword->word : "WORD", cword->doc ? cword->doc : "..."); } } } for (cword = TAILQ_FIRST(&words); cword != NULL; cword = cword_next) { cword_next = TAILQ_NEXT(cword, next); TAILQ_REMOVE(&words, cword, next); free(cword); } } } cmdenv_free(&env); return rc; } /** * Complete the given command. */ char * commands_complete(struct cmd_node *root, int argc, const char **argv, int all, int privileged) { char *word = NULL; if (_commands_execute(NULL, NULL, root, argc, argv, &word, all, privileged) == 0) return word; return NULL; } /** * Execute the given commands. */ int commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root, int argc, const char **argv, int privileged) { return _commands_execute(conn, w, root, argc, argv, NULL, 0, privileged); } /** * Check if the environment does not contain the given key. * * @param env The environment. * @param key The key to search for. * @return 1 if the environment does not contain the key. 0 otherwise. */ int cmd_check_no_env(struct cmd_env *env, const void *key) { return cmdenv_get(env, (const char *)key) == NULL; } /** * Check if the environment does contain the given key. * * @param env The environment. * @param key The key to search for. Can be a comma separated list. * @return 1 if the environment does contain the key. 0 otherwise. */ int cmd_check_env(struct cmd_env *env, const void *key) { struct cmd_env_el *el; const char *list = key; int count = 0; TAILQ_FOREACH (el, &env->elements, next) { if (contains(list, el->key)) count++; } while ((list = strchr(list, ','))) { list++; count--; } return (count == 1); } /** * Store the given key in the environment. * * @param conn The connection. * @param w The writer (not used). * @param env The environment. * @param key The key to store. * @return 1 if the key was stored */ int cmd_store_env(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, const void *key) { return cmdenv_put(env, key, NULL) != -1; } /** * Store the given key in the environment and pop one element from the stack. * * @param conn The connection. * @param w The writer (not used). * @param env The environment. * @param key The key to store. * @return 1 if the key was stored */ int cmd_store_env_and_pop(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, const void *key) { return (cmd_store_env(conn, w, env, key) != -1 && cmdenv_pop(env, 1) != -1); } /** * Store the given key with a value being the current keyword in the environment * and pop X elements from the stack. * * @param conn The connection. * @param w The writer (not used). * @param env The environment. * @param key The key to store. * @return 1 if the key was stored */ int cmd_store_env_value_and_pop(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, const void *key) { return ( cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 1) != -1); } int cmd_store_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, const void *key) { return ( cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 2) != -1); } int cmd_store_env_value(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, const void *key) { return (cmdenv_put(env, key, cmdenv_arg(env)) != -1); } int cmd_store_env_value_and_pop3(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, const void *key) { return ( cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 3) != -1); } int cmd_store_something_env_value_and_pop2(const char *what, struct cmd_env *env, const void *value) { return (cmdenv_put(env, what, value) != -1 && cmdenv_pop(env, 2) != -1); } int cmd_store_something_env_value(const char *what, struct cmd_env *env, const void *value) { return (cmdenv_put(env, what, value) != -1); } /** * Provide an iterator on all interfaces contained in "ports". * * @warning This function is not reentrant. It uses static variables to keep * track of ports that have already been provided. Moreover, to release all * resources, the iterator should be used until its end. * * @param conn The connection. * @param env The environment. * @return The next interface in the set of ports (or in all ports if no `ports` * variable is present in the environment) */ lldpctl_atom_t * cmd_iterate_on_interfaces(struct lldpctl_conn_t *conn, struct cmd_env *env) { static lldpctl_atom_iter_t *iter = NULL; static lldpctl_atom_t *iface_list = NULL; static lldpctl_atom_t *iface = NULL; const char *interfaces = cmdenv_get(env, "ports"); do { if (iter == NULL) { iface_list = lldpctl_get_interfaces(conn); if (!iface_list) { log_warnx("lldpctl", "not able to get the list of interfaces. %s", lldpctl_last_strerror(conn)); return NULL; } iter = lldpctl_atom_iter(iface_list); if (!iter) return NULL; } else { iter = lldpctl_atom_iter_next(iface_list, iter); if (iface) { lldpctl_atom_dec_ref(iface); iface = NULL; } if (!iter) { lldpctl_atom_dec_ref(iface_list); return NULL; } } iface = lldpctl_atom_iter_value(iface_list, iter); } while (interfaces && !contains(interfaces, lldpctl_atom_get_str(iface, lldpctl_k_interface_name))); return iface; } /** * Provide an iterator on all ports contained in "ports", as well as the * default port. * * @warning This function is not reentrant. It uses static variables to keep * track of ports that have already been provided. Moreover, to release all * resources, the iterator should be used until its end. * * @param conn The connection. * @param env The environment. * @param name Name of the interface (for logging purpose) * @return The next interface in the set of ports (or in all ports if no `ports` * variable is present in the environment), including the default port * if no `ports` variable is present in the environment. */ lldpctl_atom_t * cmd_iterate_on_ports(struct lldpctl_conn_t *conn, struct cmd_env *env, const char **name) { static int put_default = 0; static lldpctl_atom_t *last_port = NULL; const char *interfaces = cmdenv_get(env, "ports"); if (last_port) { lldpctl_atom_dec_ref(last_port); last_port = NULL; } if (!put_default) { lldpctl_atom_t *iface = cmd_iterate_on_interfaces(conn, env); if (iface) { *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name); last_port = lldpctl_get_port(iface); return last_port; } if (!interfaces) { put_default = 1; *name = "(default)"; last_port = lldpctl_get_default_port(conn); return last_port; } return NULL; } else { put_default = 0; return NULL; } } /** * Restrict the command to some ports. */ void cmd_restrict_ports(struct cmd_node *root) { /* Restrict to some ports. */ commands_new(commands_new(root, "ports", "Restrict configuration to some ports", cmd_check_no_env, NULL, "ports"), NULL, "Restrict configuration to the specified ports (comma-separated list)", NULL, cmd_store_env_value_and_pop2, "ports"); } /** * Restrict the command to specific protocol */ void cmd_restrict_protocol(struct cmd_node *root) { /* Restrict to some ports. */ commands_new(commands_new(root, "protocol", "Restrict to specific protocol", cmd_check_no_env, NULL, "protocol"), NULL, "Restrict display to the specified protocol", NULL, cmd_store_env_value_and_pop2, "protocol"); }