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