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