]>
Commit | Line | Data |
---|---|---|
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 | 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 Specify the Unix-domain socket used for communication with lldpd(8).\n"); | |
71 | fprintf(stderr, "-f format Choose output format (plain, keyvalue or xml).\n"); | |
72 | if (!is_lldpctl(NULL)) | |
73 | fprintf(stderr, "-c 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_info("lldpctl", "lldpd is already %s", | |
161 | pause?"paused":"resumed"); | |
162 | lldpctl_atom_dec_ref(config); | |
163 | return 0; | |
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); | |
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 | fprintf(stderr, "\n"); | |
221 | rl_forced_update_display(); | |
222 | rc = 0; | |
223 | end: | |
224 | free(line); | |
225 | tokenize_free(argc, argv); | |
226 | return rc; | |
227 | } | |
228 | ||
229 | static int | |
230 | cmd_complete(int count, int ch) | |
231 | { | |
232 | return _cmd_complete(0); | |
233 | } | |
234 | ||
235 | static int | |
236 | cmd_help(int count, int ch) | |
237 | { | |
238 | return _cmd_complete(1); | |
239 | } | |
240 | #else | |
241 | static char* | |
242 | readline() | |
243 | { | |
244 | static char line[2048]; | |
245 | fprintf(stderr, "%s", prompt()); | |
246 | fflush(stderr); | |
247 | if (fgets(line, sizeof(line) - 2, stdin) == NULL) | |
248 | return NULL; | |
249 | return line; | |
250 | } | |
251 | #endif | |
252 | ||
253 | /** | |
254 | * Execute a tokenized command and display its output. | |
255 | * | |
256 | * @param conn The connection to lldpd. | |
257 | * @param fmt Output format. | |
258 | * @param argc Number of arguments. | |
259 | * @param argv Array of arguments. | |
260 | * @return 0 if an error occurred, 1 otherwise | |
261 | */ | |
262 | static int | |
263 | cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv) | |
264 | { | |
265 | /* Init output formatter */ | |
266 | struct writer *w; | |
267 | ||
268 | if (strcmp(fmt, "plain") == 0) w = txt_init(stdout); | |
269 | else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout); | |
270 | #ifdef USE_XML | |
271 | else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout); | |
272 | #endif | |
273 | #ifdef USE_JSON | |
274 | else if (strcmp(fmt, "json") == 0) w = json_init(stdout); | |
275 | #endif | |
276 | else w = txt_init(stdout); | |
277 | ||
278 | /* Execute command */ | |
279 | int rc = commands_execute(conn, w, | |
280 | root, argc, argv); | |
281 | if (rc != 0) { | |
282 | log_info("lldpctl", "an error occurred while executing last command"); | |
283 | w->finish(w); | |
284 | return 0; | |
285 | } | |
286 | w->finish(w); | |
287 | return 1; | |
288 | } | |
289 | ||
290 | /** | |
291 | * Execute a command line and display its output. | |
292 | * | |
293 | * @param conn The connection to lldpd. | |
294 | * @param fmt Output format. | |
295 | * @param line Line to execute. | |
296 | * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise. | |
297 | */ | |
298 | static int | |
299 | parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line) | |
300 | { | |
301 | int cargc = 0; char **cargv = NULL; | |
302 | int n; | |
303 | log_debug("lldpctl", "tokenize command line"); | |
304 | n = tokenize_line(line, &cargc, &cargv); | |
305 | switch (n) { | |
306 | case -1: | |
307 | log_warnx("lldpctl", "internal error while tokenizing"); | |
308 | return -1; | |
309 | case 1: | |
310 | log_warnx("lldpctl", "unmatched quotes"); | |
311 | return -1; | |
312 | } | |
313 | if (cargc != 0) | |
314 | n = cmd_exec(conn, fmt, cargc, (const char **)cargv); | |
315 | tokenize_free(cargc, cargv); | |
316 | return (cargc == 0)?0: | |
317 | (n == 0)?-1: | |
318 | 1; | |
319 | } | |
320 | ||
321 | static struct cmd_node* | |
322 | register_commands() | |
323 | { | |
324 | root = commands_root(); | |
325 | register_commands_show(root); | |
326 | register_commands_watch(root); | |
327 | if (is_privileged()) { | |
328 | commands_new( | |
329 | commands_new(root, "update", "Update information and send LLDPU on all ports", | |
330 | NULL, NULL, NULL), | |
331 | NEWLINE, "Update information and send LLDPU on all ports", | |
332 | NULL, cmd_update, NULL); | |
333 | register_commands_configure(root); | |
334 | } | |
335 | commands_new(root, "help", "Get help on a possible command", | |
336 | NULL, cmd_store_env_and_pop, "help"); | |
337 | commands_new( | |
338 | commands_new(root, "pause", "Pause lldpd operations", NULL, NULL, NULL), | |
339 | NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL); | |
340 | commands_new( | |
341 | commands_new(root, "resume", "Resume lldpd operations", NULL, NULL, NULL), | |
342 | NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL); | |
343 | commands_new( | |
344 | commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL), | |
345 | NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL); | |
346 | return root; | |
347 | } | |
348 | ||
349 | struct input { | |
350 | TAILQ_ENTRY(input) next; | |
351 | char *name; | |
352 | }; | |
353 | TAILQ_HEAD(inputs, input); | |
354 | static int | |
355 | filter(const struct dirent *dir) | |
356 | { | |
357 | if (strlen(dir->d_name) < 5) return 0; | |
358 | if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0; | |
359 | return 1; | |
360 | } | |
361 | ||
362 | /** | |
363 | * Append a new input file/directory to the list of inputs. | |
364 | * | |
365 | * @param arg Directory or file name to add. | |
366 | * @param inputs List of inputs | |
367 | * @param acceptdir 1 if we accept a directory, 0 otherwise | |
368 | */ | |
369 | static void | |
370 | input_append(const char *arg, struct inputs *inputs, int acceptdir) | |
371 | { | |
372 | struct stat statbuf; | |
373 | if (stat(arg, &statbuf) == -1) { | |
374 | log_info("lldpctl", "cannot find configuration file/directory %s", | |
375 | arg); | |
376 | return; | |
377 | } | |
378 | ||
379 | if (!S_ISDIR(statbuf.st_mode)) { | |
380 | struct input *input = malloc(sizeof(struct input)); | |
381 | if (!input) { | |
382 | log_warn("lldpctl", "not enough memory to process %s", | |
383 | arg); | |
384 | return; | |
385 | } | |
386 | log_debug("lldpctl", "input: %s", arg); | |
387 | input->name = strdup(arg); | |
388 | TAILQ_INSERT_TAIL(inputs, input, next); | |
389 | return; | |
390 | } | |
391 | if (!acceptdir) { | |
392 | log_debug("lldpctl", "skip directory %s", | |
393 | arg); | |
394 | return; | |
395 | } | |
396 | ||
397 | struct dirent **namelist = NULL; | |
398 | int n = scandir(arg, &namelist, filter, alphasort); | |
399 | if (n < 0) { | |
400 | log_warnx("lldpctl", "unable to read directory %s", | |
401 | arg); | |
402 | return; | |
403 | } | |
404 | for (int i=0; i < n; i++) { | |
405 | char *fullname; | |
406 | if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) { | |
407 | input_append(fullname, inputs, 0); | |
408 | free(fullname); | |
409 | } | |
410 | free(namelist[i]); | |
411 | } | |
412 | free(namelist); | |
413 | } | |
414 | ||
415 | int | |
416 | main(int argc, char *argv[]) | |
417 | { | |
418 | int ch, debug = 1, rc = EXIT_FAILURE; | |
419 | const char *fmt = "plain"; | |
420 | lldpctl_conn_t *conn = NULL; | |
421 | const char *options = is_lldpctl(argv[0])?"hdvf:":"hdsvf:c:u:"; | |
422 | ||
423 | int gotinputs = 0; | |
424 | struct inputs inputs; | |
425 | TAILQ_INIT(&inputs); | |
426 | ||
427 | ctlname = lldpctl_get_default_transport(); | |
428 | ||
429 | /* Initialize logging */ | |
430 | while ((ch = getopt(argc, argv, options)) != -1) { | |
431 | switch (ch) { | |
432 | case 'd': debug++; break; | |
433 | case 's': debug--; break; | |
434 | } | |
435 | } | |
436 | log_init(debug, __progname); | |
437 | ||
438 | /* Get and parse command line options */ | |
439 | optind = 1; | |
440 | while ((ch = getopt(argc, argv, options)) != -1) { | |
441 | switch (ch) { | |
442 | case 'd': break; | |
443 | case 's': break; | |
444 | case 'h': | |
445 | usage(); | |
446 | break; | |
447 | case 'u': | |
448 | ctlname = optarg; | |
449 | break; | |
450 | case 'v': | |
451 | fprintf(stdout, "%s\n", PACKAGE_VERSION); | |
452 | exit(0); | |
453 | break; | |
454 | case 'f': | |
455 | fmt = optarg; | |
456 | break; | |
457 | case 'c': | |
458 | gotinputs = 1; | |
459 | input_append(optarg, &inputs, 1); | |
460 | break; | |
461 | default: | |
462 | usage(); | |
463 | } | |
464 | } | |
465 | ||
466 | /* Register commands */ | |
467 | root = register_commands(); | |
468 | ||
469 | /* Make a connection */ | |
470 | log_debug("lldpctl", "connect to lldpd"); | |
471 | conn = lldpctl_new_name(ctlname, NULL, NULL, NULL); | |
472 | if (conn == NULL) goto end; | |
473 | ||
474 | /* Process file inputs */ | |
475 | while (gotinputs && !TAILQ_EMPTY(&inputs)) { | |
476 | /* TAILQ_REMOVE does the right thing */ | |
477 | /* coverity[use_after_free] */ | |
478 | struct input *first = TAILQ_FIRST(&inputs); | |
479 | log_debug("lldpctl", "process: %s", first->name); | |
480 | FILE *file = fopen(first->name, "r"); | |
481 | if (file) { | |
482 | size_t len; | |
483 | char *line; | |
484 | while ((line = fgetln(file, &len))) { | |
485 | line = strndup(line, len); | |
486 | if (line[len - 1] == '\n') { | |
487 | line[len - 1] = '\0'; | |
488 | parse_and_exec(conn, fmt, line); | |
489 | } | |
490 | free(line); | |
491 | } | |
492 | fclose(file); | |
493 | } else { | |
494 | log_warn("lldpctl", "unable to open %s", | |
495 | first->name); | |
496 | } | |
497 | TAILQ_REMOVE(&inputs, first, next); | |
498 | free(first->name); | |
499 | free(first); | |
500 | } | |
501 | ||
502 | /* Process additional arguments. First if we are lldpctl (interfaces) */ | |
503 | if (is_lldpctl(NULL)) { | |
504 | char *line = NULL; | |
505 | for (int i = optind; i < argc; i++) { | |
506 | char *prev = line; | |
507 | if (asprintf(&line, "%s%s%s", | |
508 | prev?prev:"show neigh ports ", argv[i], | |
509 | (i == argc - 1)?" details":",") == -1) { | |
510 | log_warnx("lldpctl", "not enough memory to build list of interfaces"); | |
511 | free(prev); | |
512 | goto end; | |
513 | } | |
514 | free(prev); | |
515 | } | |
516 | if (line == NULL && (line = strdup("show neigh details")) == NULL) { | |
517 | log_warnx("lldpctl", "not enough memory to build command line"); | |
518 | goto end; | |
519 | } | |
520 | log_debug("lldpctl", "execute %s", line); | |
521 | if (parse_and_exec(conn, fmt, line) != -1) | |
522 | rc = EXIT_SUCCESS; | |
523 | free(line); | |
524 | goto end; | |
525 | } | |
526 | ||
527 | /* Then, if we are regular lldpcli (command line) */ | |
528 | if (optind < argc) { | |
529 | const char **cargv; | |
530 | int cargc; | |
531 | cargv = &((const char **)argv)[optind]; | |
532 | cargc = argc - optind; | |
533 | if (cmd_exec(conn, fmt, cargc, cargv) != -1) | |
534 | rc = EXIT_SUCCESS; | |
535 | goto end; | |
536 | } | |
537 | ||
538 | if (gotinputs) goto end; | |
539 | ||
540 | /* Interactive session */ | |
541 | #ifdef HAVE_LIBREADLINE | |
542 | rl_bind_key('?', cmd_help); | |
543 | rl_bind_key('\t', cmd_complete); | |
544 | #endif | |
545 | const char *line; | |
546 | do { | |
547 | if ((line = readline(prompt()))) { | |
548 | int n = parse_and_exec(conn, fmt, line); | |
549 | (void)n; | |
550 | #ifdef HAVE_READLINE_HISTORY | |
551 | if (n != 0) add_history(line); | |
552 | #endif | |
553 | } | |
554 | } while (!must_exit && line != NULL); | |
555 | rc = EXIT_SUCCESS; | |
556 | ||
557 | end: | |
558 | while (!TAILQ_EMPTY(&inputs)) { | |
559 | /* TAILQ_REMOVE does the right thing */ | |
560 | /* coverity[use_after_free] */ | |
561 | struct input *first = TAILQ_FIRST(&inputs); | |
562 | TAILQ_REMOVE(&inputs, first, next); | |
563 | free(first->name); | |
564 | free(first); | |
565 | } | |
566 | if (conn) lldpctl_release(conn); | |
567 | if (root) commands_free(root); | |
568 | return rc; | |
569 | } |