summaryrefslogtreecommitdiffstats
path: root/src/client/lldpcli.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/lldpcli.c')
-rw-r--r--src/client/lldpcli.c611
1 files changed, 611 insertions, 0 deletions
diff --git a/src/client/lldpcli.c b/src/client/lldpcli.c
new file mode 100644
index 0000000..8999ea7
--- /dev/null
+++ b/src/client/lldpcli.c
@@ -0,0 +1,611 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <libgen.h>
+#include <dirent.h>
+#include <signal.h>
+#include <sys/queue.h>
+
+#include "client.h"
+
+#ifdef HAVE___PROGNAME
+extern const char *__progname;
+#else
+# define __progname "lldpcli"
+#endif
+
+/* Global for completion */
+static struct cmd_node *root = NULL;
+const char *ctlname = NULL;
+
+static int
+is_lldpctl(const char *name)
+{
+ static int last_result = -1;
+ if (last_result == -1 && name) {
+ char *basec = strdup(name);
+ if (!basec) return 0;
+ char *bname = basename(basec);
+ last_result = (!strcmp(bname, "lldpctl"));
+ free(basec);
+ }
+ return (last_result == -1) ? 0 : last_result;
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: %s [OPTIONS ...] [COMMAND ...]\n", __progname);
+ fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "-d Enable more debugging information.\n");
+ fprintf(stderr,
+ "-u socket Specify the Unix-domain socket used for communication with lldpd(8).\n");
+ fprintf(stderr,
+ "-f format Choose output format (plain, keyvalue, json, json0"
+#if defined USE_XML
+ ", xml"
+#endif
+ ").\n");
+ if (!is_lldpctl(NULL))
+ fprintf(stderr, "-c conf Read the provided configuration file.\n");
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "See manual page lldpcli(8) for more information\n");
+ exit(1);
+}
+
+static int
+is_privileged()
+{
+ /* Check we can access the control socket with read/write
+ * privileges. The `access()` function uses the real UID and real GID,
+ * therefore we don't have to mangle with our identity. */
+ return (ctlname && access(ctlname, R_OK | W_OK) == 0);
+}
+
+static const char *
+prompt()
+{
+#define CESC "\033"
+ int privileged = is_privileged();
+ if (isatty(STDIN_FILENO)) {
+ if (privileged) return "[lldpcli] # ";
+ return "[lldpcli] $ ";
+ }
+ return "";
+}
+
+static int must_exit = 0;
+/**
+ * Exit the interpreter.
+ */
+static int
+cmd_exit(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_info("lldpctl", "quit lldpcli");
+ must_exit = 1;
+ return 1;
+}
+
+/**
+ * Send an "update" request.
+ */
+static int
+cmd_update(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_info("lldpctl", "ask for global update");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_tx_interval, -1) == NULL) {
+ log_warnx("lldpctl",
+ "unable to ask lldpd for immediate retransmission. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "immediate retransmission requested successfully");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+/**
+ * Pause or resume execution of lldpd.
+ *
+ * @param conn The connection to lldpd.
+ * @param pause 1 if we want to pause lldpd, 0 otherwise
+ * @return 1 on success, 0 on error
+ */
+static int
+cmd_pause_resume(lldpctl_conn_t *conn, int pause)
+{
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
+ log_debug("lldpctl", "lldpd is already %s",
+ pause ? "paused" : "resumed");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_paused, pause) == NULL) {
+ log_warnx("lldpctl", "unable to ask lldpd to %s operations. %s",
+ pause ? "pause" : "resume", lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "lldpd should %s operations", pause ? "pause" : "resume");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+static int
+cmd_pause(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ (void)w;
+ (void)env;
+ return cmd_pause_resume(conn, 1);
+}
+static int
+cmd_resume(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ (void)w;
+ (void)env;
+ return cmd_pause_resume(conn, 0);
+}
+
+#ifdef HAVE_LIBREADLINE
+static int
+_cmd_complete(int all)
+{
+ char **argv = NULL;
+ int argc = 0;
+ int rc = 1;
+ size_t len = strlen(rl_line_buffer);
+ char *line = malloc(len + 2);
+ if (!line) return -1;
+ strlcpy(line, rl_line_buffer, len + 2);
+ line[rl_point] = 2; /* empty character, will force a word */
+ line[rl_point + 1] = 0;
+
+ if (tokenize_line(line, &argc, &argv) != 0) goto end;
+
+ char *compl =
+ commands_complete(root, argc, (const char **)argv, all, is_privileged());
+ if (compl &&strlen(argv[argc - 1]) < strlen(compl )) {
+ if (rl_insert_text(compl +strlen(argv[argc - 1])) < 0) {
+ free(compl );
+ goto end;
+ }
+ free(compl );
+ rc = 0;
+ goto end;
+ }
+ /* No completion or several completion available. */
+ free(compl );
+ fprintf(stderr, "\n");
+ rl_forced_update_display();
+ rc = 0;
+end:
+ free(line);
+ tokenize_free(argc, argv);
+ return rc;
+}
+
+static int
+cmd_complete(int count, int ch)
+{
+ return _cmd_complete(0);
+}
+
+static int
+cmd_help(int count, int ch)
+{
+ return _cmd_complete(1);
+}
+#else
+static char *
+readline(const char *p)
+{
+ static char line[2048];
+ fprintf(stderr, "%s", p);
+ fflush(stderr);
+ if (fgets(line, sizeof(line) - 2, stdin) == NULL) return NULL;
+ return strdup(line);
+}
+#endif
+
+/**
+ * Execute a tokenized command and display its output.
+ *
+ * @param conn The connection to lldpd.
+ * @param fmt Output format.
+ * @param argc Number of arguments.
+ * @param argv Array of arguments.
+ * @return 0 if an error occurred, 1 otherwise
+ */
+static int
+cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
+{
+ /* Init output formatter */
+ struct writer *w;
+
+ if (strcmp(fmt, "plain") == 0)
+ w = txt_init(stdout);
+ else if (strcmp(fmt, "keyvalue") == 0)
+ w = kv_init(stdout);
+ else if (strcmp(fmt, "json") == 0)
+ w = json_init(stdout, 1);
+ else if (strcmp(fmt, "json0") == 0)
+ w = json_init(stdout, 0);
+#ifdef USE_XML
+ else if (strcmp(fmt, "xml") == 0)
+ w = xml_init(stdout);
+#endif
+ else {
+ log_warnx("lldpctl", "unknown output format \"%s\"", fmt);
+ w = txt_init(stdout);
+ }
+
+ /* Execute command */
+ int rc = commands_execute(conn, w, root, argc, argv, is_privileged());
+ if (rc != 0) {
+ log_info("lldpctl", "an error occurred while executing last command");
+ w->finish(w);
+ return 0;
+ }
+ w->finish(w);
+ return 1;
+}
+
+/**
+ * Execute a command line and display its output.
+ *
+ * @param conn The connection to lldpd.
+ * @param fmt Output format.
+ * @param line Line to execute.
+ * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
+ */
+static int
+parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
+{
+ int cargc = 0;
+ char **cargv = NULL;
+ int n;
+ log_debug("lldpctl", "tokenize command line");
+ n = tokenize_line(line, &cargc, &cargv);
+ switch (n) {
+ case -1:
+ log_warnx("lldpctl", "internal error while tokenizing");
+ return -1;
+ case 1:
+ log_warnx("lldpctl", "unmatched quotes");
+ return -1;
+ }
+ if (cargc != 0) n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
+ tokenize_free(cargc, cargv);
+ return (cargc == 0) ? 0 : (n == 0) ? -1 : 1;
+}
+
+static struct cmd_node *
+register_commands()
+{
+ root = commands_root();
+ register_commands_show(root);
+ register_commands_watch(root);
+ commands_new(commands_privileged(commands_new(root, "update",
+ "Update information and send LLDPU on all ports", NULL, NULL,
+ NULL)),
+ NEWLINE, "Update information and send LLDPU on all ports", NULL, cmd_update,
+ NULL);
+ register_commands_configure(root);
+ commands_hidden(commands_new(root, "complete",
+ "Get possible completions from a given command", NULL,
+ cmd_store_env_and_pop, "complete"));
+ commands_new(root, "help", "Get help on a possible command", NULL,
+ cmd_store_env_and_pop, "help");
+ commands_new(commands_new(root, "pause", "Pause lldpd operations", NULL, NULL,
+ NULL),
+ NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
+ commands_new(commands_new(root, "resume", "Resume lldpd operations", NULL, NULL,
+ NULL),
+ NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
+ commands_new(commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
+ NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
+ return root;
+}
+
+struct input {
+ TAILQ_ENTRY(input) next;
+ char *name;
+};
+TAILQ_HEAD(inputs, input);
+static int
+filter(const struct dirent *dir)
+{
+ if (strlen(dir->d_name) < 5) return 0;
+ if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
+ return 1;
+}
+
+/**
+ * Append a new input file/directory to the list of inputs.
+ *
+ * @param arg Directory or file name to add.
+ * @param inputs List of inputs
+ * @param acceptdir 1 if we accept a directory, 0 otherwise
+ */
+static void
+input_append(const char *arg, struct inputs *inputs, int acceptdir, int warn)
+{
+ struct stat statbuf;
+ if (stat(arg, &statbuf) == -1) {
+ if (warn) {
+ log_warn("lldpctl",
+ "cannot find configuration file/directory %s", arg);
+ } else {
+ log_debug("lldpctl",
+ "cannot find configuration file/directory %s", arg);
+ }
+ return;
+ }
+
+ if (!S_ISDIR(statbuf.st_mode)) {
+ struct input *input = malloc(sizeof(struct input));
+ if (!input) {
+ log_warn("lldpctl", "not enough memory to process %s", arg);
+ return;
+ }
+ log_debug("lldpctl", "input: %s", arg);
+ input->name = strdup(arg);
+ TAILQ_INSERT_TAIL(inputs, input, next);
+ return;
+ }
+ if (!acceptdir) {
+ log_debug("lldpctl", "skip directory %s", arg);
+ return;
+ }
+
+ struct dirent **namelist = NULL;
+ int n = scandir(arg, &namelist, filter, alphasort);
+ if (n < 0) {
+ log_warnx("lldpctl", "unable to read directory %s", arg);
+ return;
+ }
+ for (int i = 0; i < n; i++) {
+ char *fullname;
+ if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
+ input_append(fullname, inputs, 0, 1);
+ free(fullname);
+ }
+ free(namelist[i]);
+ }
+ free(namelist);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, debug = 0, use_syslog = 0, rc = EXIT_FAILURE;
+ const char *fmt = "plain";
+ lldpctl_conn_t *conn = NULL;
+ const char *options = is_lldpctl(argv[0]) ? "hdvf:u:" : "hdsvf:c:C:u:";
+ lldpctl_atom_t *configuration;
+
+ int gotinputs = 0, version = 0;
+ struct inputs inputs;
+ TAILQ_INIT(&inputs);
+
+ ctlname = lldpctl_get_default_transport();
+
+ signal(SIGHUP, SIG_IGN);
+
+ /* Get and parse command line options */
+ optind = 1;
+ while ((ch = getopt(argc, argv, options)) != -1) {
+ switch (ch) {
+ case 'd':
+ if (use_syslog)
+ use_syslog = 0;
+ else
+ debug++;
+ break;
+ case 's':
+ if (debug == 0)
+ use_syslog = 1;
+ else
+ debug--;
+ break;
+ case 'h':
+ usage();
+ break;
+ case 'u':
+ ctlname = optarg;
+ break;
+ case 'v':
+ version++;
+ break;
+ case 'f':
+ fmt = optarg;
+ break;
+ case 'C':
+ case 'c':
+ if (!gotinputs) {
+ log_init(use_syslog, debug, __progname);
+ lldpctl_log_level(debug + 1);
+ gotinputs = 1;
+ }
+ input_append(optarg, &inputs, 1, ch == 'c');
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (version) {
+ version_display(stdout, "lldpcli", version > 1);
+ exit(0);
+ }
+
+ if (!gotinputs) {
+ log_init(use_syslog, debug, __progname);
+ lldpctl_log_level(debug + 1);
+ }
+
+ /* Disable SIGPIPE */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Register commands */
+ root = register_commands();
+
+ /* Make a connection */
+ log_debug("lldpctl", "connect to lldpd");
+ conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
+ if (conn == NULL) goto end;
+
+ /* Check we have a working connection */
+ if ((configuration = lldpctl_get_configuration(conn)) == NULL) {
+ /* ctl.c already outputs an error */
+ goto end;
+ }
+ lldpctl_atom_dec_ref(configuration);
+
+ /* Process file inputs */
+ while (gotinputs && !TAILQ_EMPTY(&inputs)) {
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ struct input *first = TAILQ_FIRST(&inputs);
+ log_debug("lldpctl", "process: %s", first->name);
+ FILE *file = fopen(first->name, "r");
+ if (file) {
+ size_t n;
+ ssize_t len;
+ char *line;
+ while (line = NULL, len = 0,
+ (len = getline(&line, &n, file)) > 0) {
+ if (line[len - 1] == '\n') {
+ line[len - 1] = '\0';
+ parse_and_exec(conn, fmt, line);
+ }
+ free(line);
+ }
+ free(line);
+ fclose(file);
+ } else {
+ log_warn("lldpctl", "unable to open %s", first->name);
+ }
+ TAILQ_REMOVE(&inputs, first, next);
+ free(first->name);
+ free(first);
+ }
+
+ /* Process additional arguments. First if we are lldpctl (interfaces) */
+ if (is_lldpctl(NULL)) {
+ char *line = NULL;
+ for (int i = optind; i < argc; i++) {
+ char *prev = line;
+ if (asprintf(&line, "%s%s%s", prev ? prev : "show neigh ports ",
+ argv[i], (i == argc - 1) ? " details" : ",") == -1) {
+ log_warnx("lldpctl",
+ "not enough memory to build list of interfaces");
+ free(prev);
+ goto end;
+ }
+ free(prev);
+ }
+ if (line == NULL && (line = strdup("show neigh details")) == NULL) {
+ log_warnx("lldpctl", "not enough memory to build command line");
+ goto end;
+ }
+ log_debug("lldpctl", "execute %s", line);
+ if (parse_and_exec(conn, fmt, line) != -1) rc = EXIT_SUCCESS;
+ free(line);
+ goto end;
+ }
+
+ /* Then, if we are regular lldpcli (command line) */
+ if (optind < argc) {
+ const char **cargv;
+ int cargc;
+ cargv = &((const char **)argv)[optind];
+ cargc = argc - optind;
+ if (cmd_exec(conn, fmt, cargc, cargv) == 1) rc = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (gotinputs) {
+ rc = EXIT_SUCCESS;
+ goto end;
+ }
+
+ /* Interactive session */
+#ifdef HAVE_LIBREADLINE
+ rl_bind_key('?', cmd_help);
+ rl_bind_key('\t', cmd_complete);
+#endif
+ char *line = NULL;
+ do {
+ if ((line = readline(prompt()))) {
+ int n = parse_and_exec(conn, fmt, line);
+ if (n != 0) {
+#ifdef HAVE_READLINE_HISTORY
+ add_history(line);
+#endif
+ }
+ free(line);
+ }
+ } while (!must_exit && line != NULL);
+ rc = EXIT_SUCCESS;
+
+end:
+ while (!TAILQ_EMPTY(&inputs)) {
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ struct input *first = TAILQ_FIRST(&inputs);
+ TAILQ_REMOVE(&inputs, first, next);
+ free(first->name);
+ free(first);
+ }
+ if (conn) lldpctl_release(conn);
+ if (root) commands_free(root);
+ return rc;
+}