]> git.ipfire.org Git - thirdparty/bird.git/blame - client/client.c
Restricted read-only CLI.
[thirdparty/bird.git] / client / client.c
CommitLineData
ed608150
MM
1/*
2 * BIRD Client
3 *
7deffd84 4 * (c) 1999--2004 Martin Mares <mj@ucw.cz>
ed608150
MM
5 *
6 * Can be freely distributed and used under the terms of the GNU GPL.
7 */
8
9fac310d 9#include <stdio.h>
9fac310d 10#include <stdlib.h>
c51f132d 11#include <fcntl.h>
9fac310d 12#include <unistd.h>
7deffd84 13#include <termios.h>
c51f132d
MM
14#include <errno.h>
15#include <sys/socket.h>
16#include <sys/un.h>
c51f132d 17#include <sys/types.h>
7211be1c
MM
18#include <readline/readline.h>
19#include <readline/history.h>
f0333f44 20#include <curses.h>
9fac310d 21
ed608150 22#include "nest/bird.h"
9fac310d 23#include "lib/resource.h"
221135d6 24#include "lib/string.h"
ed608150 25#include "client/client.h"
4daf03e5 26#include "sysdep/unix/unix.h"
ed608150 27
e0a45fb4 28static char *opt_list = "s:vr";
c51f132d 29static int verbose;
e0a45fb4
OZ
30static char *init_cmd;
31static int once;
c51f132d 32
d69e5ff2 33static char *server_path = PATH_CONTROL_SOCKET;
c51f132d 34static int server_fd;
c51f132d
MM
35static byte server_read_buf[4096];
36static byte *server_read_pos = server_read_buf;
37
f0333f44
OZ
38#define STATE_PROMPT 0
39#define STATE_CMD_SERVER 1
40#define STATE_CMD_USER 2
41
c51f132d 42static int input_initialized;
c51f132d 43static int input_hidden_end;
f0333f44
OZ
44static int cstate = STATE_CMD_SERVER;
45static int nstate = STATE_CMD_SERVER;
46
47static int num_lines, skip_input, interactive;
c51f132d
MM
48
49/*** Parsing of arguments ***/
f50b9e48 50
9fac310d
MM
51static void
52usage(void)
53{
e0a45fb4 54 fprintf(stderr, "Usage: birdc [-s <control-socket>] [-v] [-r]\n");
9fac310d
MM
55 exit(1);
56}
57
58static void
59parse_args(int argc, char **argv)
60{
61 int c;
62
63 while ((c = getopt(argc, argv, opt_list)) >= 0)
64 switch (c)
65 {
c51f132d
MM
66 case 's':
67 server_path = optarg;
68 break;
69 case 'v':
70 verbose++;
71 break;
e0a45fb4
OZ
72 case 'r':
73 init_cmd = "restrict";
74 break;
9fac310d
MM
75 default:
76 usage();
77 }
e0a45fb4
OZ
78
79 /* If some arguments are not options, we take it as commands */
9fac310d 80 if (optind < argc)
e0a45fb4
OZ
81 {
82 char *tmp;
83 int i;
84 int len = 0;
85
86 if (init_cmd)
87 usage();
88
89 for (i = optind; i < argc; i++)
90 len += strlen(argv[i]) + 1;
91
92 tmp = init_cmd = malloc(len);
93 for (i = optind; i < argc; i++)
94 {
95 strcpy(tmp, argv[i]);
96 tmp += strlen(tmp);
97 *tmp++ = ' ';
98 }
99
100 once = 1;
101 }
9fac310d 102}
f50b9e48 103
c51f132d
MM
104/*** Input ***/
105
106static void server_send(char *);
7211be1c 107
fae0396e
MM
108/* HACK: libreadline internals we need to access */
109extern int _rl_vis_botlin;
110extern void _rl_move_vert(int);
111extern Function *rl_last_func;
112
2983460b
MM
113static int
114handle_internal_command(char *cmd)
115{
116 if (!strncmp(cmd, "exit", 4) || !strncmp(cmd, "quit", 4))
117 {
118 cleanup();
119 exit(0);
120 }
121 if (!strncmp(cmd, "help", 4))
122 {
123 puts("Press `?' for context sensitive help.");
124 return 1;
125 }
126 return 0;
127}
128
f0333f44
OZ
129void
130submit_server_command(char *cmd)
131{
132 server_send(cmd);
133 nstate = STATE_CMD_SERVER;
134 num_lines = 2;
135}
136
137
c51f132d
MM
138static void
139got_line(char *cmd_buffer)
140{
e69e4ed9
MM
141 char *cmd;
142
7211be1c 143 if (!cmd_buffer)
c51f132d
MM
144 {
145 cleanup();
146 exit(0);
147 }
7211be1c 148 if (cmd_buffer[0])
c51f132d 149 {
e69e4ed9
MM
150 cmd = cmd_expand(cmd_buffer);
151 if (cmd)
152 {
153 add_history(cmd);
f0333f44 154
2983460b 155 if (!handle_internal_command(cmd))
f0333f44
OZ
156 submit_server_command(cmd);
157
e69e4ed9
MM
158 free(cmd);
159 }
971b2310
MM
160 else
161 add_history(cmd_buffer);
c51f132d
MM
162 }
163 free(cmd_buffer);
164}
165
fae0396e
MM
166void
167input_start_list(void) /* Leave the currently edited line and make space for listing */
168{
169 _rl_move_vert(_rl_vis_botlin);
bd62eeca 170#ifdef HAVE_RL_CRLF
59b96d7b 171 rl_crlf();
bd62eeca 172#endif
fae0396e
MM
173}
174
175void
176input_stop_list(void) /* Reprint the currently edited line after listing */
177{
178 rl_on_new_line();
179 rl_redisplay();
180}
181
0223d4ff 182static int
d7390312 183input_complete(int arg UNUSED, int key UNUSED)
0223d4ff 184{
fae0396e
MM
185 static int complete_flag;
186 char buf[256];
187
188 if (rl_last_func != input_complete)
189 complete_flag = 0;
190 switch (cmd_complete(rl_line_buffer, rl_point, buf, complete_flag))
191 {
192 case 0:
193 complete_flag = 1;
194 break;
195 case 1:
196 rl_insert_text(buf);
197 break;
198 default:
199 complete_flag = 1;
bd62eeca 200#ifdef HAVE_RL_DING
59b96d7b 201 rl_ding();
bd62eeca 202#endif
fae0396e 203 }
0223d4ff
MM
204 return 0;
205}
206
207static int
d7390312 208input_help(int arg, int key UNUSED)
0223d4ff 209{
cf186034 210 int i, in_string, in_bracket;
0223d4ff 211
fae0396e 212 if (arg != 1)
0223d4ff 213 return rl_insert(arg, '?');
80ac7dc1 214
cf186034 215 in_string = in_bracket = 0;
80ac7dc1 216 for (i = 0; i < rl_point; i++)
0223d4ff 217 {
cf186034 218
80ac7dc1
OZ
219 if (rl_line_buffer[i] == '"')
220 in_string = ! in_string;
cf186034
OZ
221 else if (! in_string)
222 {
223 if (rl_line_buffer[i] == '[')
224 in_bracket++;
225 else if (rl_line_buffer[i] == ']')
226 in_bracket--;
227 }
0223d4ff 228 }
80ac7dc1
OZ
229
230 /* `?' inside string or path -> insert */
cf186034 231 if (in_string || in_bracket)
80ac7dc1
OZ
232 return rl_insert(1, '?');
233
fae0396e
MM
234 rl_begin_undo_group(); /* HACK: We want to display `?' at point position */
235 rl_insert_text("?");
0223d4ff 236 rl_redisplay();
fae0396e
MM
237 rl_end_undo_group();
238 input_start_list();
239 cmd_help(rl_line_buffer, rl_point);
240 rl_undo_command(1, 0);
241 input_stop_list();
0223d4ff
MM
242 return 0;
243}
244
c51f132d
MM
245static void
246input_init(void)
247{
248 rl_readline_name = "birdc";
0223d4ff
MM
249 rl_add_defun("bird-complete", input_complete, '\t');
250 rl_add_defun("bird-help", input_help, '?');
c51f132d
MM
251 rl_callback_handler_install("bird> ", got_line);
252 input_initialized = 1;
661ec5db
OZ
253// readline library does strange things when stdin is nonblocking.
254// if (fcntl(0, F_SETFL, O_NONBLOCK) < 0)
255// die("fcntl: %m");
c51f132d
MM
256}
257
258static void
259input_hide(void)
260{
f0333f44
OZ
261 input_hidden_end = rl_end;
262 rl_end = 0;
263 rl_expand_prompt("");
264 rl_redisplay();
c51f132d
MM
265}
266
267static void
268input_reveal(void)
269{
f0333f44
OZ
270 /* need this, otherwise some lib seems to eat pending output when
271 the prompt is displayed */
272 fflush(stdout);
273 tcdrain(fileno(stdout));
274
c51f132d
MM
275 rl_end = input_hidden_end;
276 rl_expand_prompt("bird> ");
277 rl_forced_update_display();
c51f132d
MM
278}
279
280void
281cleanup(void)
282{
283 if (input_initialized)
284 {
285 input_initialized = 0;
286 input_hide();
287 rl_callback_handler_remove();
288 }
289}
290
f0333f44
OZ
291void
292update_state(void)
293{
294 if (nstate == cstate)
295 return;
296
e0a45fb4
OZ
297 if (init_cmd)
298 {
299 /* First transition - client received hello from BIRD
300 and there is waiting initial command */
301 submit_server_command(init_cmd);
302 init_cmd = NULL;
303 return;
304 }
305
306 if (!init_cmd && once)
307 {
308 /* Initial command is finished and we want to exit */
309 cleanup();
310 exit(0);
311 }
312
f0333f44
OZ
313 if (nstate == STATE_PROMPT)
314 if (input_initialized)
315 input_reveal();
316 else
317 input_init();
318
319 if (nstate != STATE_PROMPT)
320 input_hide();
321
322 cstate = nstate;
323}
324
325void
326more(void)
327{
328 printf("--More--\015");
329 fflush(stdout);
330
331 redo:
332 switch (getchar())
333 {
334 case 32:
335 num_lines = 2;
336 break;
337 case 13:
338 num_lines--;
339 break;
340 case 'q':
341 skip_input = 1;
342 break;
343 default:
344 goto redo;
345 }
346
347 printf(" \015");
348 fflush(stdout);
349}
350
351
c51f132d
MM
352/*** Communication with server ***/
353
354static void
355server_connect(void)
356{
357 struct sockaddr_un sa;
358
359 server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
360 if (server_fd < 0)
361 die("Cannot create socket: %m");
68fa95cf
OZ
362
363 if (strlen(server_path) >= sizeof(sa.sun_path))
364 die("server_connect: path too long");
365
c51f132d
MM
366 bzero(&sa, sizeof(sa));
367 sa.sun_family = AF_UNIX;
97c6fa02 368 strcpy(sa.sun_path, server_path);
0b3bf4b1 369 if (connect(server_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) < 0)
c51f132d
MM
370 die("Unable to connect to server control socket (%s): %m", server_path);
371 if (fcntl(server_fd, F_SETFL, O_NONBLOCK) < 0)
372 die("fcntl: %m");
373}
374
375static void
376server_got_reply(char *x)
377{
378 int code;
9c46ad8e 379 int len = 0;
c51f132d 380
c51f132d 381 if (*x == '+') /* Async reply */
9c46ad8e 382 skip_input || (len = printf(">>> %s\n", x+1));
c51f132d 383 else if (x[0] == ' ') /* Continuation */
9c46ad8e 384 skip_input || (len = printf("%s%s\n", verbose ? " " : "", x+1));
c51f132d
MM
385 else if (strlen(x) > 4 &&
386 sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 &&
387 (x[4] == ' ' || x[4] == '-'))
388 {
389 if (code)
9c46ad8e 390 skip_input || (len = printf("%s\n", verbose ? x : x+5));
c51f132d 391 if (x[4] == ' ')
f0333f44
OZ
392 {
393 nstate = STATE_PROMPT;
394 skip_input = 0;
395 return;
396 }
c51f132d
MM
397 }
398 else
9c46ad8e 399 skip_input || (len = printf("??? <%s>\n", x));
f0333f44
OZ
400
401 if (skip_input)
402 return;
403
9c46ad8e
OZ
404 if (interactive && input_initialized && (len > 0))
405 {
406 int lns = LINES ? LINES : 25;
407 int cls = COLS ? COLS : 80;
408 num_lines += (len + cls - 1) / cls; /* Divide and round up */
409 if ((num_lines >= lns) && (cstate == STATE_CMD_SERVER))
410 more();
411 }
c51f132d
MM
412}
413
414static void
415server_read(void)
416{
417 int c;
418 byte *start, *p;
419
e0011590 420 redo:
c51f132d
MM
421 c = read(server_fd, server_read_pos, server_read_buf + sizeof(server_read_buf) - server_read_pos);
422 if (!c)
423 die("Connection closed by server.");
424 if (c < 0)
e0011590
OZ
425 {
426 if (errno == EINTR)
427 goto redo;
428 else
429 die("Server read error: %m");
430 }
431
c51f132d
MM
432 start = server_read_buf;
433 p = server_read_pos;
434 server_read_pos += c;
435 while (p < server_read_pos)
436 if (*p++ == '\n')
437 {
438 p[-1] = 0;
439 server_got_reply(start);
440 start = p;
441 }
442 if (start != server_read_buf)
443 {
444 int l = server_read_pos - start;
445 memmove(server_read_buf, start, l);
446 server_read_pos = server_read_buf + l;
447 }
448 else if (server_read_pos == server_read_buf + sizeof(server_read_buf))
449 {
450 strcpy(server_read_buf, "?<too-long>");
451 server_read_pos = server_read_buf + 11;
452 }
453}
454
455static fd_set select_fds;
456
457static void
f0333f44 458select_loop(void)
c51f132d 459{
e0011590 460 int rv;
f0333f44 461 while (1)
c51f132d 462 {
9e85a5e6 463 FD_ZERO(&select_fds);
f0333f44
OZ
464
465 if (cstate != STATE_CMD_USER)
466 FD_SET(server_fd, &select_fds);
467 if (cstate != STATE_CMD_SERVER)
c51f132d 468 FD_SET(0, &select_fds);
e0011590
OZ
469
470 rv = select(server_fd+1, &select_fds, NULL, NULL, NULL);
471 if (rv < 0)
472 {
473 if (errno == EINTR)
474 continue;
475 else
476 die("select: %m");
477 }
478
c51f132d
MM
479 if (FD_ISSET(server_fd, &select_fds))
480 {
481 server_read();
f0333f44 482 update_state();
c51f132d 483 }
f0333f44 484
c51f132d 485 if (FD_ISSET(0, &select_fds))
f0333f44
OZ
486 {
487 rl_callback_read_char();
488 update_state();
489 }
c51f132d 490 }
c51f132d
MM
491}
492
e0011590
OZ
493static void
494wait_for_write(int fd)
495{
496 while (1)
497 {
498 int rv;
499 fd_set set;
500 FD_ZERO(&set);
501 FD_SET(fd, &set);
502
503 rv = select(fd+1, NULL, &set, NULL, NULL);
504 if (rv < 0)
505 {
506 if (errno == EINTR)
507 continue;
508 else
509 die("select: %m");
510 }
511
512 if (FD_ISSET(server_fd, &set))
513 return;
514 }
515}
516
c51f132d
MM
517static void
518server_send(char *cmd)
519{
520 int l = strlen(cmd);
521 byte *z = alloca(l + 1);
522
523 memcpy(z, cmd, l);
524 z[l++] = '\n';
525 while (l)
526 {
527 int cnt = write(server_fd, z, l);
e0011590 528
c51f132d
MM
529 if (cnt < 0)
530 {
e0011590
OZ
531 if (errno == EAGAIN)
532 wait_for_write(server_fd);
533 else if (errno == EINTR)
534 continue;
c51f132d
MM
535 else
536 die("Server write error: %m");
537 }
538 else
539 {
540 l -= cnt;
541 z += cnt;
542 }
543 }
7211be1c
MM
544}
545
ed608150
MM
546int
547main(int argc, char **argv)
548{
9fac310d
MM
549#ifdef HAVE_LIBDMALLOC
550 if (!getenv("DMALLOC_OPTIONS"))
551 dmalloc_debug(0x2f03d00);
552#endif
553
f0333f44 554 interactive = isatty(0);
9fac310d 555 parse_args(argc, argv);
0223d4ff 556 cmd_build_tree();
c51f132d 557 server_connect();
f0333f44 558 select_loop();
c51f132d 559 return 0;
ed608150 560}