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