]> git.ipfire.org Git - thirdparty/lldpd.git/blob - src/client/lldpcli.c
lldpcli: don't complain when reading a commented line
[thirdparty/lldpd.git] / src / client / lldpcli.c
1 /* -*- mode: c; c-file-style: "openbsd" -*- */
2 /*
3 * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <time.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/socket.h>
28 #include <sys/un.h>
29 #include <arpa/inet.h>
30 #include <libgen.h>
31 #include <dirent.h>
32 #include <signal.h>
33 #include <sys/queue.h>
34
35 #include "client.h"
36
37 #ifdef HAVE___PROGNAME
38 extern const char *__progname;
39 #else
40 # define __progname "lldpcli"
41 #endif
42
43 /* Global for completion */
44 static struct cmd_node *root = NULL;
45 const char *ctlname = NULL;
46
47 static int
48 is_lldpctl(const char *name)
49 {
50 static int last_result = -1;
51 if (last_result == -1 && name) {
52 char *basec = strdup(name);
53 if (!basec) return 0;
54 char *bname = basename(basec);
55 last_result = (!strcmp(bname, "lldpctl"));
56 free(basec);
57 }
58 return (last_result == -1)?0:last_result;
59 }
60
61 static void
62 usage()
63 {
64 fprintf(stderr, "Usage: %s [OPTIONS ...] [COMMAND ...]\n", __progname);
65 fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
66
67 fprintf(stderr, "\n");
68
69 fprintf(stderr, "-d Enable more debugging information.\n");
70 fprintf(stderr, "-u socket Specify the Unix-domain socket used for communication with lldpd(8).\n");
71 fprintf(stderr, "-f format Choose output format (plain, keyvalue, json or xml).\n");
72 if (!is_lldpctl(NULL))
73 fprintf(stderr, "-c conf Read the provided configuration file.\n");
74
75 fprintf(stderr, "\n");
76
77 fprintf(stderr, "see manual page lldpcli(8) for more information\n");
78 exit(1);
79 }
80
81 static int
82 is_privileged()
83 {
84 /* Check we can access the control socket with read/write
85 * privileges. The `access()` function uses the real UID and real GID,
86 * therefore we don't have to mangle with our identity. */
87 return (ctlname && access(ctlname, R_OK|W_OK) == 0);
88 }
89
90 static char*
91 prompt()
92 {
93 #define CESC "\033"
94 int privileged = is_privileged();
95 if (isatty(STDIN_FILENO)) {
96 if (privileged)
97 return "[lldpcli] # ";
98 return "[lldpcli] $ ";
99 }
100 return "";
101 }
102
103 static int must_exit = 0;
104 /**
105 * Exit the interpreter.
106 */
107 static int
108 cmd_exit(struct lldpctl_conn_t *conn, struct writer *w,
109 struct cmd_env *env, void *arg)
110 {
111 log_info("lldpctl", "quit lldpcli");
112 must_exit = 1;
113 return 1;
114 }
115
116 /**
117 * Send an "update" request.
118 */
119 static int
120 cmd_update(struct lldpctl_conn_t *conn, struct writer *w,
121 struct cmd_env *env, void *arg)
122 {
123 log_info("lldpctl", "ask for global update");
124
125 lldpctl_atom_t *config = lldpctl_get_configuration(conn);
126 if (config == NULL) {
127 log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
128 lldpctl_last_strerror(conn));
129 return 0;
130 }
131 if (lldpctl_atom_set_int(config,
132 lldpctl_k_config_tx_interval, -1) == NULL) {
133 log_warnx("lldpctl", "unable to ask lldpd for immediate retransmission. %s",
134 lldpctl_last_strerror(conn));
135 lldpctl_atom_dec_ref(config);
136 return 0;
137 }
138 log_info("lldpctl", "immediate retransmission requested successfuly");
139 lldpctl_atom_dec_ref(config);
140 return 1;
141 }
142
143 /**
144 * Pause or resume execution of lldpd.
145 *
146 * @param conn The connection to lldpd.
147 * @param pause 1 if we want to pause lldpd, 0 otherwise
148 * @return 1 on success, 0 on error
149 */
150 static int
151 cmd_pause_resume(lldpctl_conn_t *conn, int pause)
152 {
153 lldpctl_atom_t *config = lldpctl_get_configuration(conn);
154 if (config == NULL) {
155 log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
156 lldpctl_last_strerror(conn));
157 return 0;
158 }
159 if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
160 log_debug("lldpctl", "lldpd is already %s",
161 pause?"paused":"resumed");
162 lldpctl_atom_dec_ref(config);
163 return 1;
164 }
165 if (lldpctl_atom_set_int(config,
166 lldpctl_k_config_paused, pause) == NULL) {
167 log_warnx("lldpctl", "unable to ask lldpd to %s operations. %s",
168 pause?"pause":"resume",
169 lldpctl_last_strerror(conn));
170 lldpctl_atom_dec_ref(config);
171 return 0;
172 }
173 log_info("lldpctl", "lldpd should %s operations",
174 pause?"pause":"resume");
175 lldpctl_atom_dec_ref(config);
176 return 1;
177 }
178 static int
179 cmd_pause(struct lldpctl_conn_t *conn, struct writer *w,
180 struct cmd_env *env, void *arg) {
181 (void)w; (void)env;
182 return cmd_pause_resume(conn, 1);
183 }
184 static int
185 cmd_resume(struct lldpctl_conn_t *conn, struct writer *w,
186 struct cmd_env *env, void *arg) {
187 (void)w; (void)env;
188 return cmd_pause_resume(conn, 0);
189 }
190
191
192 #ifdef HAVE_LIBREADLINE
193 static int
194 _cmd_complete(int all)
195 {
196 char **argv = NULL;
197 int argc = 0;
198 int rc = 1;
199 size_t len = strlen(rl_line_buffer);
200 char *line = malloc(len + 2);
201 if (!line) return -1;
202 strlcpy(line, rl_line_buffer, len + 2);
203 line[rl_point] = 2; /* empty character, will force a word */
204 line[rl_point+1] = 0;
205
206 if (tokenize_line(line, &argc, &argv) != 0)
207 goto end;
208
209 char *compl = commands_complete(root, argc, (const char **)argv, all, is_privileged());
210 if (compl && strlen(argv[argc-1]) < strlen(compl)) {
211 if (rl_insert_text(compl + strlen(argv[argc-1])) < 0) {
212 free(compl);
213 goto end;
214 }
215 free(compl);
216 rc = 0;
217 goto end;
218 }
219 /* No completion or several completion available. */
220 free(compl);
221 fprintf(stderr, "\n");
222 rl_forced_update_display();
223 rc = 0;
224 end:
225 free(line);
226 tokenize_free(argc, argv);
227 return rc;
228 }
229
230 static int
231 cmd_complete(int count, int ch)
232 {
233 return _cmd_complete(0);
234 }
235
236 static int
237 cmd_help(int count, int ch)
238 {
239 return _cmd_complete(1);
240 }
241 #else
242 static char*
243 readline(const char *p)
244 {
245 static char line[2048];
246 fprintf(stderr, "%s", p);
247 fflush(stderr);
248 if (fgets(line, sizeof(line) - 2, stdin) == NULL)
249 return NULL;
250 return line;
251 }
252 #endif
253
254 /**
255 * Execute a tokenized command and display its output.
256 *
257 * @param conn The connection to lldpd.
258 * @param fmt Output format.
259 * @param argc Number of arguments.
260 * @param argv Array of arguments.
261 * @return 0 if an error occurred, 1 otherwise
262 */
263 static int
264 cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
265 {
266 /* Init output formatter */
267 struct writer *w;
268
269 if (strcmp(fmt, "plain") == 0) w = txt_init(stdout);
270 else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout);
271 #ifdef USE_XML
272 else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout);
273 #endif
274 #ifdef USE_JANSSON
275 else if (strcmp(fmt, "json") == 0) w = jansson_init(stdout);
276 #endif
277 #ifdef USE_JSONC
278 else if (strcmp(fmt, "json") == 0) w = jsonc_init(stdout);
279 #endif
280 else w = txt_init(stdout);
281
282 /* Execute command */
283 int rc = commands_execute(conn, w,
284 root, argc, argv, is_privileged());
285 if (rc != 0) {
286 log_info("lldpctl", "an error occurred while executing last command");
287 w->finish(w);
288 return 0;
289 }
290 w->finish(w);
291 return 1;
292 }
293
294 /**
295 * Execute a command line and display its output.
296 *
297 * @param conn The connection to lldpd.
298 * @param fmt Output format.
299 * @param line Line to execute.
300 * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
301 */
302 static int
303 parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
304 {
305 int cargc = 0; char **cargv = NULL;
306 int n;
307 log_debug("lldpctl", "tokenize command line");
308 n = tokenize_line(line, &cargc, &cargv);
309 switch (n) {
310 case -1:
311 log_warnx("lldpctl", "internal error while tokenizing");
312 return -1;
313 case 1:
314 log_warnx("lldpctl", "unmatched quotes");
315 return -1;
316 }
317 if (cargc != 0)
318 n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
319 tokenize_free(cargc, cargv);
320 return (cargc == 0)?0:
321 (n == 0)?-1:
322 1;
323 }
324
325 static struct cmd_node*
326 register_commands()
327 {
328 root = commands_root();
329 register_commands_show(root);
330 register_commands_watch(root);
331 commands_privileged(commands_new(
332 commands_new(root, "update", "Update information and send LLDPU on all ports",
333 NULL, NULL, NULL),
334 NEWLINE, "Update information and send LLDPU on all ports",
335 NULL, cmd_update, NULL));
336 register_commands_configure(root);
337 commands_hidden(commands_new(root, "complete", "Get possible completions from a given command",
338 NULL, cmd_store_env_and_pop, "complete"));
339 commands_new(root, "help", "Get help on a possible command",
340 NULL, cmd_store_env_and_pop, "help");
341 commands_new(
342 commands_new(root, "pause", "Pause lldpd operations", NULL, NULL, NULL),
343 NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
344 commands_new(
345 commands_new(root, "resume", "Resume lldpd operations", NULL, NULL, NULL),
346 NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
347 commands_new(
348 commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
349 NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
350 return root;
351 }
352
353 struct input {
354 TAILQ_ENTRY(input) next;
355 char *name;
356 };
357 TAILQ_HEAD(inputs, input);
358 static int
359 filter(const struct dirent *dir)
360 {
361 if (strlen(dir->d_name) < 5) return 0;
362 if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
363 return 1;
364 }
365
366 /**
367 * Append a new input file/directory to the list of inputs.
368 *
369 * @param arg Directory or file name to add.
370 * @param inputs List of inputs
371 * @param acceptdir 1 if we accept a directory, 0 otherwise
372 */
373 static void
374 input_append(const char *arg, struct inputs *inputs, int acceptdir)
375 {
376 struct stat statbuf;
377 if (stat(arg, &statbuf) == -1) {
378 log_info("lldpctl", "cannot find configuration file/directory %s",
379 arg);
380 return;
381 }
382
383 if (!S_ISDIR(statbuf.st_mode)) {
384 struct input *input = malloc(sizeof(struct input));
385 if (!input) {
386 log_warn("lldpctl", "not enough memory to process %s",
387 arg);
388 return;
389 }
390 log_debug("lldpctl", "input: %s", arg);
391 input->name = strdup(arg);
392 TAILQ_INSERT_TAIL(inputs, input, next);
393 return;
394 }
395 if (!acceptdir) {
396 log_debug("lldpctl", "skip directory %s",
397 arg);
398 return;
399 }
400
401 struct dirent **namelist = NULL;
402 int n = scandir(arg, &namelist, filter, alphasort);
403 if (n < 0) {
404 log_warnx("lldpctl", "unable to read directory %s",
405 arg);
406 return;
407 }
408 for (int i=0; i < n; i++) {
409 char *fullname;
410 if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
411 input_append(fullname, inputs, 0);
412 free(fullname);
413 }
414 free(namelist[i]);
415 }
416 free(namelist);
417 }
418
419 int
420 main(int argc, char *argv[])
421 {
422 int ch, debug = 1, rc = EXIT_FAILURE;
423 const char *fmt = "plain";
424 lldpctl_conn_t *conn = NULL;
425 const char *options = is_lldpctl(argv[0])?"hdvf:":"hdsvf:c:u:";
426
427 int gotinputs = 0;
428 struct inputs inputs;
429 TAILQ_INIT(&inputs);
430
431 ctlname = lldpctl_get_default_transport();
432
433 signal(SIGHUP, SIG_IGN);
434
435 /* Initialize logging */
436 while ((ch = getopt(argc, argv, options)) != -1) {
437 switch (ch) {
438 case 'd': debug++; break;
439 case 's': debug--; break;
440 }
441 }
442 log_init(debug, __progname);
443
444 /* Get and parse command line options */
445 optind = 1;
446 while ((ch = getopt(argc, argv, options)) != -1) {
447 switch (ch) {
448 case 'd': break;
449 case 's': break;
450 case 'h':
451 usage();
452 break;
453 case 'u':
454 ctlname = optarg;
455 break;
456 case 'v':
457 fprintf(stdout, "%s\n", PACKAGE_VERSION);
458 exit(0);
459 break;
460 case 'f':
461 fmt = optarg;
462 break;
463 case 'c':
464 gotinputs = 1;
465 input_append(optarg, &inputs, 1);
466 break;
467 default:
468 usage();
469 }
470 }
471
472 /* Disable SIGPIPE */
473 signal(SIGPIPE, SIG_IGN);
474
475 /* Register commands */
476 root = register_commands();
477
478 /* Make a connection */
479 log_debug("lldpctl", "connect to lldpd");
480 conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
481 if (conn == NULL) goto end;
482
483 /* Process file inputs */
484 while (gotinputs && !TAILQ_EMPTY(&inputs)) {
485 /* coverity[use_after_free]
486 TAILQ_REMOVE does the right thing */
487 struct input *first = TAILQ_FIRST(&inputs);
488 log_debug("lldpctl", "process: %s", first->name);
489 FILE *file = fopen(first->name, "r");
490 if (file) {
491 size_t len;
492 char *line;
493 while ((line = fgetln(file, &len))) {
494 line = strndup(line, len);
495 if (line[len - 1] == '\n') {
496 line[len - 1] = '\0';
497 if (line[0] != '#')
498 parse_and_exec(conn, fmt, line);
499 }
500 free(line);
501 }
502 fclose(file);
503 } else {
504 log_warn("lldpctl", "unable to open %s",
505 first->name);
506 }
507 TAILQ_REMOVE(&inputs, first, next);
508 free(first->name);
509 free(first);
510 }
511
512 /* Process additional arguments. First if we are lldpctl (interfaces) */
513 if (is_lldpctl(NULL)) {
514 char *line = NULL;
515 for (int i = optind; i < argc; i++) {
516 char *prev = line;
517 if (asprintf(&line, "%s%s%s",
518 prev?prev:"show neigh ports ", argv[i],
519 (i == argc - 1)?" details":",") == -1) {
520 log_warnx("lldpctl", "not enough memory to build list of interfaces");
521 free(prev);
522 goto end;
523 }
524 free(prev);
525 }
526 if (line == NULL && (line = strdup("show neigh details")) == NULL) {
527 log_warnx("lldpctl", "not enough memory to build command line");
528 goto end;
529 }
530 log_debug("lldpctl", "execute %s", line);
531 if (parse_and_exec(conn, fmt, line) != -1)
532 rc = EXIT_SUCCESS;
533 free(line);
534 goto end;
535 }
536
537 /* Then, if we are regular lldpcli (command line) */
538 if (optind < argc) {
539 const char **cargv;
540 int cargc;
541 cargv = &((const char **)argv)[optind];
542 cargc = argc - optind;
543 if (cmd_exec(conn, fmt, cargc, cargv) != -1)
544 rc = EXIT_SUCCESS;
545 goto end;
546 }
547
548 if (gotinputs) {
549 rc = EXIT_SUCCESS;
550 goto end;
551 }
552
553 /* Interactive session */
554 #ifdef HAVE_LIBREADLINE
555 rl_bind_key('?', cmd_help);
556 rl_bind_key('\t', cmd_complete);
557 #endif
558 const char *line;
559 do {
560 if ((line = readline(prompt()))) {
561 int n = parse_and_exec(conn, fmt, line);
562 (void)n;
563 #ifdef HAVE_READLINE_HISTORY
564 if (n != 0) add_history(line);
565 #endif
566 }
567 } while (!must_exit && line != NULL);
568 rc = EXIT_SUCCESS;
569
570 end:
571 while (!TAILQ_EMPTY(&inputs)) {
572 /* coverity[use_after_free]
573 TAILQ_REMOVE does the right thing */
574 struct input *first = TAILQ_FIRST(&inputs);
575 TAILQ_REMOVE(&inputs, first, next);
576 free(first->name);
577 free(first);
578 }
579 if (conn) lldpctl_release(conn);
580 if (root) commands_free(root);
581 return rc;
582 }