]>
Commit | Line | Data |
---|---|---|
a5e9f3d2 OZ |
1 | /* |
2 | * BIRD Client | |
3 | * | |
4 | * (c) 1999--2004 Martin Mares <mj@ucw.cz> | |
5 | * (c) 2013 Tomas Hlavacek <tmshlvck@gmail.com> | |
6 | * | |
7 | * Can be freely distributed and used under the terms of the GNU GPL. | |
8 | */ | |
9 | ||
10 | /** | |
11 | * DOC: BIRD client | |
12 | * | |
13 | * There are two variants of BIRD client: regular and light. regular | |
14 | * variant depends on readline and ncurses libraries, while light | |
15 | * variant uses just libc. Most of the code and the main() is common | |
16 | * for both variants (in client.c file) and just a few functions are | |
17 | * different (in birdc.c for regular and birdcl.c for light). Two | |
18 | * binaries are generated by linking common object files like client.o | |
19 | * (which is compiled from client.c just once) with either birdc.o or | |
20 | * birdcl.o for each variant. | |
21 | */ | |
22 | ||
23 | #include <stdio.h> | |
24 | #include <stdlib.h> | |
25 | #include <fcntl.h> | |
26 | #include <unistd.h> | |
27 | #include <errno.h> | |
28 | #include <sys/socket.h> | |
29 | #include <sys/un.h> | |
30 | #include <sys/types.h> | |
31 | ||
32 | #include "nest/bird.h" | |
33 | #include "lib/resource.h" | |
34 | #include "lib/string.h" | |
35 | #include "client/client.h" | |
36 | #include "sysdep/unix/unix.h" | |
37 | ||
38 | #define SERVER_READ_BUF_LEN 4096 | |
39 | ||
f2ae2bad | 40 | static char *opt_list = "s:vrl"; |
a5e9f3d2 OZ |
41 | static int verbose, restricted, once; |
42 | static char *init_cmd; | |
43 | ||
44 | static char *server_path = PATH_CONTROL_SOCKET; | |
45 | static int server_fd; | |
46 | static byte server_read_buf[SERVER_READ_BUF_LEN]; | |
47 | static byte *server_read_pos = server_read_buf; | |
48 | ||
49 | int init = 1; /* During intial sequence */ | |
50 | int busy = 1; /* Executing BIRD command */ | |
51 | int interactive; /* Whether stdin is terminal */ | |
52 | ||
53 | static int num_lines, skip_input; | |
54 | int term_lns, term_cls; | |
55 | ||
56 | ||
57 | /*** Parsing of arguments ***/ | |
58 | ||
59 | static void | |
60 | usage(char *name) | |
61 | { | |
f2ae2bad | 62 | fprintf(stderr, "Usage: %s [-s <control-socket>] [-v] [-r] [-l]\n", name); |
a5e9f3d2 OZ |
63 | exit(1); |
64 | } | |
65 | ||
66 | static void | |
67 | parse_args(int argc, char **argv) | |
68 | { | |
f2ae2bad | 69 | int server_changed = 0; |
a5e9f3d2 OZ |
70 | int c; |
71 | ||
72 | while ((c = getopt(argc, argv, opt_list)) >= 0) | |
73 | switch (c) | |
74 | { | |
75 | case 's': | |
76 | server_path = optarg; | |
f2ae2bad | 77 | server_changed = 1; |
a5e9f3d2 OZ |
78 | break; |
79 | case 'v': | |
80 | verbose++; | |
81 | break; | |
82 | case 'r': | |
83 | restricted = 1; | |
84 | break; | |
f2ae2bad OZ |
85 | case 'l': |
86 | if (!server_changed) | |
87 | server_path = xbasename(server_path); | |
88 | break; | |
a5e9f3d2 OZ |
89 | default: |
90 | usage(argv[0]); | |
91 | } | |
92 | ||
93 | /* If some arguments are not options, we take it as commands */ | |
94 | if (optind < argc) | |
95 | { | |
96 | char *tmp; | |
97 | int i; | |
98 | int len = 0; | |
99 | ||
100 | for (i = optind; i < argc; i++) | |
101 | len += strlen(argv[i]) + 1; | |
102 | ||
103 | tmp = init_cmd = malloc(len); | |
104 | for (i = optind; i < argc; i++) | |
105 | { | |
106 | strcpy(tmp, argv[i]); | |
107 | tmp += strlen(tmp); | |
108 | *tmp++ = ' '; | |
109 | } | |
110 | tmp[-1] = 0; | |
111 | ||
112 | once = 1; | |
113 | interactive = 0; | |
114 | } | |
115 | } | |
116 | ||
117 | ||
118 | /*** Input ***/ | |
119 | ||
120 | static void server_send(char *cmd); | |
121 | ||
122 | static int | |
123 | handle_internal_command(char *cmd) | |
124 | { | |
125 | if (!strncmp(cmd, "exit", 4) || !strncmp(cmd, "quit", 4)) | |
126 | { | |
127 | cleanup(); | |
128 | exit(0); | |
129 | } | |
130 | if (!strncmp(cmd, "help", 4)) | |
131 | { | |
132 | puts("Press `?' for context sensitive help."); | |
133 | return 1; | |
134 | } | |
135 | return 0; | |
136 | } | |
137 | ||
138 | static void | |
139 | submit_server_command(char *cmd) | |
140 | { | |
141 | busy = 1; | |
142 | num_lines = 2; | |
143 | server_send(cmd); | |
144 | } | |
145 | ||
8137fe6d OZ |
146 | static inline void |
147 | submit_init_command(char *cmd_raw) | |
148 | { | |
149 | char *cmd = cmd_expand(cmd_raw); | |
150 | ||
151 | if (!cmd) | |
152 | { | |
153 | cleanup(); | |
154 | exit(0); | |
155 | } | |
156 | ||
157 | submit_server_command(cmd); | |
158 | free(cmd); | |
159 | } | |
160 | ||
a5e9f3d2 OZ |
161 | void |
162 | submit_command(char *cmd_raw) | |
163 | { | |
164 | char *cmd = cmd_expand(cmd_raw); | |
165 | ||
166 | if (!cmd) | |
167 | return; | |
168 | ||
169 | if (!handle_internal_command(cmd)) | |
170 | submit_server_command(cmd); | |
171 | ||
172 | free(cmd); | |
173 | } | |
174 | ||
175 | static void | |
176 | init_commands(void) | |
177 | { | |
178 | if (restricted) | |
179 | { | |
180 | submit_server_command("restrict"); | |
181 | restricted = 0; | |
182 | return; | |
183 | } | |
184 | ||
185 | if (init_cmd) | |
186 | { | |
187 | /* First transition - client received hello from BIRD | |
188 | and there is waiting initial command */ | |
8137fe6d | 189 | submit_init_command(init_cmd); |
a5e9f3d2 OZ |
190 | init_cmd = NULL; |
191 | return; | |
192 | } | |
193 | ||
194 | if (once) | |
195 | { | |
196 | /* Initial command is finished and we want to exit */ | |
197 | cleanup(); | |
198 | exit(0); | |
199 | } | |
200 | ||
201 | input_init(); | |
4d4979c6 OZ |
202 | |
203 | term_lns = (term_lns > 0) ? term_lns : 25; | |
204 | term_cls = (term_cls > 0) ? term_cls : 80; | |
205 | ||
a5e9f3d2 OZ |
206 | init = 0; |
207 | } | |
208 | ||
209 | ||
210 | /*** Output ***/ | |
211 | ||
212 | void | |
213 | more(void) | |
214 | { | |
215 | more_begin(); | |
216 | printf("--More--\015"); | |
217 | fflush(stdout); | |
218 | ||
219 | redo: | |
220 | switch (getchar()) | |
221 | { | |
222 | case ' ': | |
223 | num_lines = 2; | |
224 | break; | |
225 | case '\n': | |
226 | case '\r': | |
227 | num_lines--; | |
228 | break; | |
229 | case 'q': | |
230 | skip_input = 1; | |
231 | break; | |
232 | default: | |
233 | goto redo; | |
234 | } | |
235 | ||
236 | printf(" \015"); | |
237 | fflush(stdout); | |
238 | more_end(); | |
239 | } | |
240 | ||
241 | ||
242 | /*** Communication with server ***/ | |
243 | ||
244 | static void | |
245 | server_connect(void) | |
246 | { | |
247 | struct sockaddr_un sa; | |
248 | ||
249 | server_fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
250 | if (server_fd < 0) | |
3f2c7600 | 251 | DIE("Cannot create socket"); |
a5e9f3d2 OZ |
252 | |
253 | if (strlen(server_path) >= sizeof(sa.sun_path)) | |
254 | die("server_connect: path too long"); | |
255 | ||
256 | bzero(&sa, sizeof(sa)); | |
257 | sa.sun_family = AF_UNIX; | |
258 | strcpy(sa.sun_path, server_path); | |
259 | if (connect(server_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) < 0) | |
3f2c7600 | 260 | DIE("Unable to connect to server control socket (%s)", server_path); |
a5e9f3d2 | 261 | if (fcntl(server_fd, F_SETFL, O_NONBLOCK) < 0) |
3f2c7600 | 262 | DIE("fcntl"); |
a5e9f3d2 OZ |
263 | } |
264 | ||
265 | ||
266 | #define PRINTF(LEN, PARGS...) do { if (!skip_input) len = printf(PARGS); } while(0) | |
267 | ||
268 | static void | |
269 | server_got_reply(char *x) | |
270 | { | |
271 | int code; | |
272 | int len = 0; | |
273 | ||
274 | if (*x == '+') /* Async reply */ | |
275 | PRINTF(len, ">>> %s\n", x+1); | |
276 | else if (x[0] == ' ') /* Continuation */ | |
277 | PRINTF(len, "%s%s\n", verbose ? " " : "", x+1); | |
278 | else if (strlen(x) > 4 && | |
279 | sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 && | |
280 | (x[4] == ' ' || x[4] == '-')) | |
281 | { | |
282 | if (code) | |
283 | PRINTF(len, "%s\n", verbose ? x : x+5); | |
284 | ||
285 | if (x[4] == ' ') | |
286 | { | |
287 | busy = 0; | |
288 | skip_input = 0; | |
289 | return; | |
290 | } | |
291 | } | |
292 | else | |
293 | PRINTF(len, "??? <%s>\n", x); | |
294 | ||
295 | if (interactive && busy && !skip_input && !init && (len > 0)) | |
296 | { | |
297 | num_lines += (len + term_cls - 1) / term_cls; /* Divide and round up */ | |
298 | if (num_lines >= term_lns) | |
299 | more(); | |
300 | } | |
301 | } | |
302 | ||
303 | static void | |
304 | server_read(void) | |
305 | { | |
306 | int c; | |
307 | byte *start, *p; | |
308 | ||
309 | redo: | |
310 | c = read(server_fd, server_read_pos, server_read_buf + sizeof(server_read_buf) - server_read_pos); | |
311 | if (!c) | |
3f2c7600 | 312 | die("Connection closed by server"); |
a5e9f3d2 OZ |
313 | if (c < 0) |
314 | { | |
315 | if (errno == EINTR) | |
316 | goto redo; | |
317 | else | |
3f2c7600 | 318 | DIE("Server read error"); |
a5e9f3d2 OZ |
319 | } |
320 | ||
321 | start = server_read_buf; | |
322 | p = server_read_pos; | |
323 | server_read_pos += c; | |
324 | while (p < server_read_pos) | |
325 | if (*p++ == '\n') | |
326 | { | |
327 | p[-1] = 0; | |
328 | server_got_reply(start); | |
329 | start = p; | |
330 | } | |
331 | if (start != server_read_buf) | |
332 | { | |
333 | int l = server_read_pos - start; | |
334 | memmove(server_read_buf, start, l); | |
335 | server_read_pos = server_read_buf + l; | |
336 | } | |
337 | else if (server_read_pos == server_read_buf + sizeof(server_read_buf)) | |
338 | { | |
339 | strcpy(server_read_buf, "?<too-long>"); | |
340 | server_read_pos = server_read_buf + 11; | |
341 | } | |
342 | } | |
343 | ||
344 | static void | |
345 | select_loop(void) | |
346 | { | |
347 | int rv; | |
348 | while (1) | |
349 | { | |
350 | if (init && !busy) | |
351 | init_commands(); | |
352 | ||
353 | if (!init) | |
354 | input_notify(!busy); | |
355 | ||
356 | fd_set select_fds; | |
357 | FD_ZERO(&select_fds); | |
358 | ||
359 | FD_SET(server_fd, &select_fds); | |
360 | if (!busy) | |
361 | FD_SET(0, &select_fds); | |
362 | ||
363 | rv = select(server_fd+1, &select_fds, NULL, NULL, NULL); | |
364 | if (rv < 0) | |
365 | { | |
366 | if (errno == EINTR) | |
367 | continue; | |
368 | else | |
3f2c7600 | 369 | DIE("select"); |
a5e9f3d2 OZ |
370 | } |
371 | ||
372 | if (FD_ISSET(0, &select_fds)) | |
373 | { | |
374 | input_read(); | |
375 | continue; | |
376 | } | |
377 | ||
378 | if (FD_ISSET(server_fd, &select_fds)) | |
379 | { | |
380 | server_read(); | |
381 | continue; | |
382 | } | |
383 | } | |
384 | } | |
385 | ||
386 | static void | |
387 | wait_for_write(int fd) | |
388 | { | |
389 | while (1) | |
390 | { | |
391 | int rv; | |
392 | fd_set set; | |
393 | FD_ZERO(&set); | |
394 | FD_SET(fd, &set); | |
395 | ||
396 | rv = select(fd+1, NULL, &set, NULL, NULL); | |
397 | if (rv < 0) | |
398 | { | |
399 | if (errno == EINTR) | |
400 | continue; | |
401 | else | |
3f2c7600 | 402 | DIE("select"); |
a5e9f3d2 OZ |
403 | } |
404 | ||
405 | if (FD_ISSET(server_fd, &set)) | |
406 | return; | |
407 | } | |
408 | } | |
409 | ||
410 | static void | |
411 | server_send(char *cmd) | |
412 | { | |
413 | int l = strlen(cmd); | |
414 | byte *z = alloca(l + 1); | |
415 | ||
416 | memcpy(z, cmd, l); | |
417 | z[l++] = '\n'; | |
418 | while (l) | |
419 | { | |
420 | int cnt = write(server_fd, z, l); | |
421 | ||
422 | if (cnt < 0) | |
423 | { | |
424 | if (errno == EAGAIN) | |
425 | wait_for_write(server_fd); | |
426 | else if (errno == EINTR) | |
427 | continue; | |
428 | else | |
3f2c7600 | 429 | DIE("Server write error"); |
a5e9f3d2 OZ |
430 | } |
431 | else | |
432 | { | |
433 | l -= cnt; | |
434 | z += cnt; | |
435 | } | |
436 | } | |
437 | } | |
438 | ||
a5e9f3d2 OZ |
439 | int |
440 | main(int argc, char **argv) | |
441 | { | |
442 | interactive = isatty(0); | |
443 | parse_args(argc, argv); | |
444 | cmd_build_tree(); | |
445 | server_connect(); | |
446 | select_loop(); | |
447 | return 0; | |
448 | } |