/* * BIRD Client * * (c) 1999--2004 Martin Mares * (c) 2013 Tomas Hlavacek * * Can be freely distributed and used under the terms of the GNU GPL. */ /** * DOC: BIRD client * * There are two variants of BIRD client: regular and light. regular * variant depends on readline and ncurses libraries, while light * variant uses just libc. Most of the code and the main() is common * for both variants (in client.c file) and just a few functions are * different (in birdc.c for regular and birdcl.c for light). Two * binaries are generated by linking common object files like client.o * (which is compiled from client.c just once) with either birdc.o or * birdcl.o for each variant. */ #include #include #include #include #include #include #include #include #include #include "nest/bird.h" #include "lib/resource.h" #include "lib/string.h" #include "client/client.h" #include "client/print_cbor.c" #include "sysdep/unix/unix.h" #define SERVER_READ_BUF_LEN 4096 static char *opt_list = "s:vrl"; static int verbose, restricted, once; static char *init_cmd; static char *server_path = PATH_CONTROL_SOCKET; static int server_fd; static byte server_read_buf[SERVER_READ_BUF_LEN]; static byte *server_read_pos = server_read_buf; int init = 1; /* During intial sequence */ int busy = 1; /* Executing BIRD command */ int interactive; /* Whether stdin is terminal */ int last_code; /* Last return code */ int cbor_mode; /* Convert to cbor and push to yi socket (and convert answer back) */ static int num_lines, skip_input; int term_lns, term_cls; /*** Parsing of arguments ***/ static void usage(char *name) { fprintf(stderr, "Usage: %s [-s ] [-v] [-r] [-l]\n", name); exit(1); } static void parse_args(int argc, char **argv) { int server_changed = 0; int c; while ((c = getopt(argc, argv, opt_list)) >= 0) switch (c) { case 's': server_path = optarg; server_changed = 1; break; case 'v': verbose++; break; case 'r': restricted = 1; break; case 'l': if (!server_changed) server_path = xbasename(server_path); break; default: usage(argv[0]); } /* If some arguments are not options, we take it as commands */ if (optind < argc) { char *tmp; int i; int len = 0; for (i = optind; i < argc; i++) len += strlen(argv[i]) + 1; tmp = init_cmd = malloc(len); for (i = optind; i < argc; i++) { strcpy(tmp, argv[i]); tmp += strlen(tmp); *tmp++ = ' '; } tmp[-1] = 0; once = 1; interactive = 0; } } /*** Input ***/ static void server_send(char *cmd); static void server_send_byte(byte *cmd, int l); static int handle_internal_command(char *cmd) { if (!strncmp(cmd, "exit", 4) || !strncmp(cmd, "quit", 4)) { cleanup(); exit(0); } if (!strncmp(cmd, "help", 4)) { puts("Press `?' for context sensitive help."); return 1; } return 0; } uint compare_string(byte *str1, uint length, const char *str2) { if (length < strlen(str2)) { return 0; } for (size_t i = 0; i < strlen(str2); i++) { if (str1[i]!=str2[i]) { return 0; } } return 1; } void write_args_cbor(char *cmd_buffer, struct cbor_writer *w) { size_t l = strlen(cmd_buffer); size_t pt = 0; while (cmd_buffer[pt] == ' ') pt++; if (pt+1 >= l) { return; } cbor_add_string(w, "args"); cbor_open_list(w); while (l>pt) { size_t next_space = 0; while (next_space + pt < l && cmd_buffer[next_space + pt] != ' ' && l>pt) next_space++; cbor_open_block_with_length(w, 1); cbor_add_string(w, "arg"); cbor_nonterminated_string(w, &cmd_buffer[pt], next_space); pt += next_space; while (cmd_buffer[pt] == ' ') pt++; } cbor_close_block_or_list(w); } int serial_num = 0; void make_cmd_cbor(char *cmd_buffer) { size_t l = strlen(cmd_buffer); char cbor_buf[l*10]; struct linpool *lp = lp_new(&root_pool); struct cbor_writer *w = cbor_init(cbor_buf, l*10, lp); write_item(w, 6, 24); // tag 24 - cbor binary int length_pt = w->pt + 1; cbor_write_item_with_constant_val_length_4(w, 2, 0); cbor_open_list_with_length(w, 2); cbor_write_item_with_constant_val_length_4(w, 0, serial_num); cbor_open_block_with_length(w, 1); cbor_add_string(w, "command:do"); char *show = "show "; int buf_pt = 0; if (compare_string(cmd_buffer, l, show)) { cbor_open_block(w); buf_pt += strlen(show); l -= strlen(show); if (compare_string(&cmd_buffer[buf_pt], l, "memory")) { cbor_string_int(w, "command", SHOW_MEMORY); cbor_close_block_or_list(w); rewrite_4bytes_int(w, length_pt, w->pt); server_send_byte(cbor_buf, w->pt); lp_flush(lp); return; } else if (compare_string(&cmd_buffer[buf_pt], l, "status")) { cbor_string_int(w, "command", SHOW_STATUS); cbor_close_block_or_list(w); rewrite_4bytes_int(w, length_pt, w->pt); server_send_byte(cbor_buf, w->pt); lp_flush(lp); return; } else if (compare_string(&cmd_buffer[buf_pt], l, "symbols")) { cbor_string_int(w, "command", SHOW_SYMBOLS); write_args_cbor(&cmd_buffer[buf_pt + strlen("symbols ")], w); cbor_close_block_or_list(w); rewrite_4bytes_int(w, length_pt, w->pt); server_send_byte(cbor_buf, w->pt); lp_flush(lp); return; } else if (compare_string(&cmd_buffer[buf_pt], l, "ospf")) { cbor_string_int(w, "command", SHOW_OSPF); write_args_cbor(&cmd_buffer[buf_pt + strlen("ospf")], w); cbor_close_block_or_list(w); rewrite_4bytes_int(w, length_pt, w->pt); server_send_byte(cbor_buf, w->pt); lp_flush(lp); return; } else if (compare_string(&cmd_buffer[buf_pt], l, "protocols")) { printf("protocols\n"); cbor_string_int(w, "command", SHOW_PROTOCOLS); write_args_cbor(&cmd_buffer[buf_pt + strlen("protocols")], w); cbor_close_block_or_list(w); rewrite_4bytes_int(w, length_pt, w->pt); server_send_byte(cbor_buf, w->pt); lp_flush(lp); return; } else { printf("this command is not implemented yet\n"); } } else if (compare_string(cmd_buffer, l, "down")) { cbor_add_string(w, "down"); rewrite_4bytes_int(w, length_pt, w->pt); server_send_byte(cbor_buf, w->pt); die("Shutdown from client"); return; } lp_flush(lp); printf("this command is not implemented yet\n"); } static void submit_server_command(char *cmd) { if (cbor_mode) { make_cmd_cbor(cmd); return; } busy = 1; num_lines = 2; fprintf(stderr, "cmd: %s \n", cmd); server_send(cmd); } static inline void submit_init_command(char *cmd_raw) { char *cmd = cmd_expand(cmd_raw); if (!cmd) { cleanup(); exit(1); } submit_server_command(cmd); free(cmd); } void submit_command(char *cmd_raw) { char *cmd = cmd_expand(cmd_raw); if (!cmd) return; if (!handle_internal_command(cmd)) submit_server_command(cmd); free(cmd); } static void init_commands(void) { if (restricted) { submit_server_command("restrict"); restricted = 0; return; } if (init_cmd) { /* First transition - client received hello from BIRD and there is waiting initial command */ submit_init_command(init_cmd); init_cmd = NULL; return; } if (once) { /* Initial command is finished and we want to exit */ cleanup(); exit((last_code < 8000) ? 0 : 1); } input_init(); term_lns = (term_lns > 0) ? term_lns : 25; term_cls = (term_cls > 0) ? term_cls : 80; init = 0; } /*** Output ***/ void more(void) { more_begin(); printf("--More--\015"); fflush(stdout); redo: switch (getchar()) { case ' ': num_lines = 2; break; case '\n': case '\r': num_lines--; break; case 'q': skip_input = 1; break; default: goto redo; } printf(" \015"); fflush(stdout); more_end(); } /*** Communication with server ***/ static void server_connect(void) { struct sockaddr_un sa; server_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (server_fd < 0) DIE("Cannot create socket"); if (strlen(server_path) >= sizeof(sa.sun_path)) die("server_connect: path too long"); bzero(&sa, sizeof(sa)); sa.sun_family = AF_UNIX; strcpy(sa.sun_path, server_path); if (connect(server_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) < 0) DIE("Unable to connect to server control socket (%s)", server_path); if (fcntl(server_fd, F_SETFL, O_NONBLOCK) < 0) DIE("fcntl"); fprintf(stdout, "Socket: %s connected ok\n", server_path); } #define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0) static void server_got_reply(char *x) { int code; int len = 0; if (*x == '+') /* Async reply */ PRINTF(len, ">>> %s\n", x+1); else if (x[0] == ' ') /* Continuation */ PRINTF(len, "%s%s\n", verbose ? " " : "", x+1); else if (strlen(x) > 4 && sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 && (x[4] == ' ' || x[4] == '-')) { if (code) PRINTF(len, "%s\n", verbose ? x : x+5); last_code = code; if (x[4] == ' ') { busy = 0; skip_input = 0; return; } } else PRINTF(len, "??? <%s>\n", x); if (interactive && busy && !skip_input && !init && (len > 0)) { num_lines += (len + term_cls - 1) / term_cls; /* Divide and round up */ if (num_lines >= term_lns) more(); } } void server_got_binary(int c) { if (cbor_mode == 0) { byte expected[] = {0x87, 0x42, 0x49, 0x52, 0x44, 0x0D, 0x0A, 0x1A, 0x0A, 0x01}; if (c < 10) die("too short header"); for (int i = 0; i < 9; i++) { if (server_read_buf[i] != expected[i]) die("wrong header"); } if (server_read_buf[9] != expected[9]) die("unknown version of binary communication"); cbor_mode = 1; } else { int length = 0; for (int i = 0; i < 4; i++) { length = length << 8; length += server_read_buf[3 + i]; } if (length > c - 7) { byte bigger_buf[length]; memcpy(bigger_buf, server_read_buf, c); int cc = read(server_fd, &bigger_buf[c], length - c + 7); if (!cc) die("Connection closed by server"); if (cc < 0) { DIE("Server read error"); } print_cbor_response(&bigger_buf[13], length - 6); } else { print_cbor_response(&server_read_buf[13], c); } } busy = 0; skip_input = 0; } static void server_read(void) { int c; byte *start, *p; redo: c = read(server_fd, server_read_pos, server_read_buf + sizeof(server_read_buf) - server_read_pos); if (!c) die("Connection closed by server"); if (c < 0) { if (errno == EINTR) goto redo; else DIE("Server read error"); } if ((init && (*server_read_buf == 0x87)) || cbor_mode) return server_got_binary(c); start = server_read_buf; p = server_read_pos; server_read_pos += c; while (p < server_read_pos) if (*p++ == '\n') { p[-1] = 0; server_got_reply(start); start = p; } if (start != server_read_buf) { int l = server_read_pos - start; memmove(server_read_buf, start, l); server_read_pos = server_read_buf + l; } else if (server_read_pos == server_read_buf + sizeof(server_read_buf)) { strcpy(server_read_buf, "?"); server_read_pos = server_read_buf + 11; } } static void select_loop(void) { int rv; while (1) { if (init && !busy) init_commands(); if (!init) input_notify(!busy); fd_set select_fds; FD_ZERO(&select_fds); FD_SET(server_fd, &select_fds); if (!busy) FD_SET(0, &select_fds); rv = select(server_fd+1, &select_fds, NULL, NULL, NULL); if (rv < 0) { if (errno == EINTR) continue; else DIE("select"); } if (FD_ISSET(0, &select_fds)) { input_read(); continue; } if (FD_ISSET(server_fd, &select_fds)) { server_read(); continue; } } } static void wait_for_write(int fd) { while (1) { int rv; fd_set set; FD_ZERO(&set); FD_SET(fd, &set); rv = select(fd+1, NULL, &set, NULL, NULL); if (rv < 0) { if (errno == EINTR) continue; else DIE("select"); } if (FD_ISSET(server_fd, &set)) return; } } static void server_send(char *cmd) { int l = strlen(cmd); byte *z = alloca(l + 1); memcpy(z, cmd, l); z[l++] = '\n'; while (l) { int cnt = write(server_fd, z, l); if (cnt < 0) { if (errno == EAGAIN) wait_for_write(server_fd); else if (errno == EINTR) continue; else DIE("Server write error"); } else { l -= cnt; z += cnt; } } } static void server_send_byte(byte *cmd, int l) { byte *z = alloca(l); memcpy(z, cmd, l); while (l) { int cnt = write(server_fd, z, l); if (cnt < 0) { if (errno == EAGAIN) wait_for_write(server_fd); else if (errno == EINTR) continue; else DIE("Server write error"); } else { l -= cnt; z += cnt; } } } int main(int argc, char **argv) { interactive = isatty(0); parse_args(argc, argv); cmd_build_tree(); resource_init(); server_connect(); select_loop(); return 0; }