detach stdin/stdout/stderr from child processes, implement a separate logging pipe...
[project/netifd.git] / proto-shell.c
1 #define _GNU_SOURCE
2
3 #include <string.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <glob.h>
7 #include <unistd.h>
8 #include <fcntl.h>
9 #include <signal.h>
10
11 #include <arpa/inet.h>
12 #include <netinet/in.h>
13
14 #include <libubox/blobmsg_json.h>
15
16 #include "netifd.h"
17 #include "interface.h"
18 #include "interface-ip.h"
19 #include "proto.h"
20
21 static struct netifd_fd proto_fd;
22
23 struct proto_shell_handler {
24 struct list_head list;
25 struct proto_handler proto;
26 struct config_param_list config;
27 char *config_buf;
28 char script_name[];
29 };
30
31 struct proto_shell_state {
32 struct interface_proto_state proto;
33 struct proto_shell_handler *handler;
34 struct blob_attr *config;
35
36 struct device_user l3_dev;
37
38 struct uloop_timeout setup_timeout;
39 struct netifd_process setup_task;
40 struct netifd_process teardown_task;
41 bool teardown_pending;
42 bool teardown_wait_task;
43
44 struct netifd_process proto_task;
45 };
46
47 static int
48 proto_shell_handler(struct interface_proto_state *proto,
49 enum interface_proto_cmd cmd, bool force)
50 {
51 struct proto_shell_state *state;
52 struct proto_shell_handler *handler;
53 struct netifd_process *proc;
54 const char *argv[6];
55 const char *action;
56 char *config;
57 int ret, i = 0;
58
59 state = container_of(proto, struct proto_shell_state, proto);
60 handler = state->handler;
61
62 if (cmd == PROTO_CMD_SETUP) {
63 action = "setup";
64 proc = &state->setup_task;
65 } else {
66 action = "teardown";
67 proc = &state->teardown_task;
68 if (state->setup_task.uloop.pending && !state->teardown_wait_task) {
69 uloop_timeout_set(&state->setup_timeout, 1000);
70 kill(state->setup_task.uloop.pid, SIGTERM);
71 state->teardown_pending = true;
72 return 0;
73 }
74 }
75
76 config = blobmsg_format_json(state->config, true);
77 if (!config)
78 return -1;
79
80 argv[i++] = handler->script_name;
81 argv[i++] = handler->proto.name;
82 argv[i++] = action;
83 argv[i++] = proto->iface->name;
84 argv[i++] = config;
85 if (proto->iface->main_dev.dev)
86 argv[i++] = proto->iface->main_dev.dev->ifname;
87 argv[i] = NULL;
88
89 ret = netifd_start_process(argv, NULL, proc);
90 free(config);
91
92 return ret;
93 }
94
95 static void
96 proto_shell_setup_timeout_cb(struct uloop_timeout *timeout)
97 {
98 struct proto_shell_state *state;
99
100 state = container_of(timeout, struct proto_shell_state, setup_timeout);
101 kill(state->setup_task.uloop.pid, SIGKILL);
102 }
103
104 static void
105 proto_shell_setup_cb(struct netifd_process *p, int ret)
106 {
107 struct proto_shell_state *state;
108
109 state = container_of(p, struct proto_shell_state, setup_task);
110 uloop_timeout_cancel(&state->setup_timeout);
111 if (state->teardown_pending) {
112 state->teardown_pending = false;
113 proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false);
114 }
115 }
116
117 static void
118 proto_shell_teardown_cb(struct netifd_process *p, int ret)
119 {
120 struct proto_shell_state *state;
121
122 state = container_of(p, struct proto_shell_state, teardown_task);
123
124 if (state->teardown_wait_task)
125 return;
126
127 netifd_kill_process(&state->proto_task);
128
129 if (state->l3_dev.dev)
130 device_remove_user(&state->l3_dev);
131
132 state->proto.proto_event(&state->proto, IFPEV_DOWN);
133 }
134
135 static void
136 proto_shell_task_cb(struct netifd_process *p, int ret)
137 {
138 struct proto_shell_state *state;
139 bool teardown_wait_task;
140
141 state = container_of(p, struct proto_shell_state, proto_task);
142
143 teardown_wait_task = state->teardown_wait_task;
144 state->teardown_wait_task = false;
145 if (state->teardown_pending || state->teardown_task.uloop.pending)
146 return;
147
148 if (teardown_wait_task) {
149 proto_shell_teardown_cb(&state->teardown_task, 0);
150 return;
151 }
152
153 state->proto.proto_event(&state->proto, IFPEV_LINK_LOST);
154 proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false);
155 }
156
157 static void
158 proto_shell_free(struct interface_proto_state *proto)
159 {
160 struct proto_shell_state *state;
161
162 state = container_of(proto, struct proto_shell_state, proto);
163 free(state->config);
164 free(state);
165 }
166
167 static void
168 proto_shell_parse_addr_list(struct interface *iface, struct blob_attr *attr,
169 bool v6, bool external)
170 {
171 struct device_addr *addr;
172 struct blob_attr *cur;
173 int rem;
174
175 blobmsg_for_each_attr(cur, attr, rem) {
176 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) {
177 DPRINTF("Ignore wrong address type: %d\n", blobmsg_type(cur));
178 continue;
179 }
180
181 addr = proto_parse_ip_addr_string(blobmsg_data(cur), v6, v6 ? 32 : 128);
182 if (!addr) {
183 DPRINTF("Failed to parse IP address string: %s\n", (char *) blobmsg_data(cur));
184 continue;
185 }
186
187 if (external)
188 addr->flags |= DEVADDR_EXTERNAL;
189
190 vlist_add(&iface->proto_addr, &addr->node);
191 }
192 }
193
194 enum {
195 ROUTE_TARGET,
196 ROUTE_MASK,
197 ROUTE_GATEWAY,
198 ROUTE_DEVICE,
199 __ROUTE_LAST
200 };
201
202 static const struct blobmsg_policy route_attr[__ROUTE_LAST] = {
203 [ROUTE_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
204 [ROUTE_MASK] = { .name = "mask", .type = BLOBMSG_TYPE_STRING },
205 [ROUTE_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING },
206 [ROUTE_DEVICE] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
207 };
208
209 static void
210 parse_route(struct interface *iface, struct blob_attr *attr, bool v6)
211 {
212 struct blob_attr *tb[__ROUTE_LAST], *cur;
213 struct device_route *route;
214 int af = v6 ? AF_INET6 : AF_INET;
215
216 blobmsg_parse(route_attr, __ROUTE_LAST, tb, blobmsg_data(attr), blobmsg_data_len(attr));
217
218 if (!tb[ROUTE_GATEWAY] && !tb[ROUTE_DEVICE])
219 return;
220
221 route = calloc(1, sizeof(*route));
222 if (!route)
223 return;
224
225 route->mask = v6 ? 128 : 32;
226 if ((cur = tb[ROUTE_MASK]) != NULL) {
227 route->mask = parse_netmask_string(blobmsg_data(cur), v6);
228 if (route->mask > (v6 ? 128 : 32))
229 goto error;
230 }
231
232 if ((cur = tb[ROUTE_TARGET]) != NULL) {
233 if (!inet_pton(af, blobmsg_data(cur), &route->addr)) {
234 DPRINTF("Failed to parse route target: %s\n", (char *) blobmsg_data(cur));
235 goto error;
236 }
237 }
238
239 if ((cur = tb[ROUTE_GATEWAY]) != NULL) {
240 if (!inet_pton(af, blobmsg_data(cur), &route->nexthop)) {
241 DPRINTF("Failed to parse route gateway: %s\n", (char *) blobmsg_data(cur));
242 goto error;
243 }
244 }
245
246 if ((cur = tb[ROUTE_DEVICE]) != NULL)
247 route->device = device_get(blobmsg_data(cur), true);
248
249 vlist_add(&iface->proto_route, &route->node);
250 return;
251
252 error:
253 free(route);
254 }
255
256 static void
257 proto_shell_parse_route_list(struct interface *iface, struct blob_attr *attr,
258 bool v6)
259 {
260 struct blob_attr *cur;
261 int rem;
262
263 blobmsg_for_each_attr(cur, attr, rem) {
264 if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) {
265 DPRINTF("Ignore wrong route type: %d\n", blobmsg_type(cur));
266 continue;
267 }
268
269 parse_route(iface, cur, v6);
270 }
271 }
272
273 enum {
274 NOTIFY_ACTION,
275 NOTIFY_COMMAND,
276 NOTIFY_ENV,
277 NOTIFY_SIGNAL,
278 NOTIFY_LINK_UP,
279 NOTIFY_IFNAME,
280 NOTIFY_ADDR_EXT,
281 NOTIFY_IPADDR,
282 NOTIFY_IP6ADDR,
283 NOTIFY_ROUTES,
284 NOTIFY_ROUTES6,
285 NOTIFY_DNS,
286 NOTIFY_DNS_SEARCH,
287 __NOTIFY_LAST
288 };
289
290 static const struct blobmsg_policy notify_attr[__NOTIFY_LAST] = {
291 [NOTIFY_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_INT32 },
292 [NOTIFY_COMMAND] = { .name = "command", .type = BLOBMSG_TYPE_ARRAY },
293 [NOTIFY_ENV] = { .name = "env", .type = BLOBMSG_TYPE_ARRAY },
294 [NOTIFY_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 },
295 [NOTIFY_LINK_UP] = { .name = "link-up", .type = BLOBMSG_TYPE_BOOL },
296 [NOTIFY_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
297 [NOTIFY_ADDR_EXT] = { .name = "address-external", .type = BLOBMSG_TYPE_BOOL },
298 [NOTIFY_IPADDR] = { .name = "ipaddr", .type = BLOBMSG_TYPE_ARRAY },
299 [NOTIFY_IP6ADDR] = { .name = "ip6addr", .type = BLOBMSG_TYPE_ARRAY },
300 [NOTIFY_ROUTES] = { .name = "routes", .type = BLOBMSG_TYPE_ARRAY },
301 [NOTIFY_ROUTES6] = { .name = "routes6", .type = BLOBMSG_TYPE_ARRAY },
302 [NOTIFY_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY },
303 [NOTIFY_DNS_SEARCH] = { .name = "dns_search", .type = BLOBMSG_TYPE_ARRAY },
304 };
305
306 static int
307 proto_shell_update_link(struct proto_shell_state *state, struct blob_attr **tb)
308 {
309 struct blob_attr *cur;
310 bool addr_ext = false;
311 bool up;
312
313 if (!tb[NOTIFY_LINK_UP])
314 return UBUS_STATUS_INVALID_ARGUMENT;
315
316 up = blobmsg_get_bool(tb[NOTIFY_LINK_UP]);
317 if (!up) {
318 state->proto.proto_event(&state->proto, IFPEV_LINK_LOST);
319 return 0;
320 }
321
322 if (!tb[NOTIFY_IFNAME]) {
323 if (!state->proto.iface->main_dev.dev)
324 return UBUS_STATUS_INVALID_ARGUMENT;
325 } else if (!state->l3_dev.dev) {
326 device_add_user(&state->l3_dev,
327 device_get(blobmsg_data(tb[NOTIFY_IFNAME]), true));
328 device_claim(&state->l3_dev);
329 state->proto.iface->l3_dev = &state->l3_dev;
330 }
331
332 interface_ip_update_start(state->proto.iface);
333
334 if ((cur = tb[NOTIFY_ADDR_EXT]) != NULL)
335 addr_ext = blobmsg_get_bool(cur);
336
337 if ((cur = tb[NOTIFY_IPADDR]) != NULL)
338 proto_shell_parse_addr_list(state->proto.iface, cur, false, addr_ext);
339
340 if ((cur = tb[NOTIFY_IP6ADDR]) != NULL)
341 proto_shell_parse_addr_list(state->proto.iface, cur, true, addr_ext);
342
343 if ((cur = tb[NOTIFY_ROUTES]) != NULL)
344 proto_shell_parse_route_list(state->proto.iface, cur, false);
345
346 if ((cur = tb[NOTIFY_ROUTES6]) != NULL)
347 proto_shell_parse_route_list(state->proto.iface, cur, true);
348
349 if ((cur = tb[NOTIFY_DNS]) != NULL)
350 interface_add_dns_server_list(state->proto.iface, cur);
351
352 if ((cur = tb[NOTIFY_DNS_SEARCH]) != NULL)
353 interface_add_dns_search_list(state->proto.iface, cur);
354
355 interface_ip_update_complete(state->proto.iface);
356
357 state->proto.proto_event(&state->proto, IFPEV_UP);
358
359 return 0;
360 }
361
362 static bool
363 fill_string_list(struct blob_attr *attr, char **argv, int max)
364 {
365 struct blob_attr *cur;
366 int argc = 0;
367 int rem;
368
369 if (!attr)
370 goto out;
371
372 blobmsg_for_each_attr(cur, attr, rem) {
373 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
374 return false;
375
376 if (!blobmsg_check_attr(cur, NULL))
377 return false;
378
379 argv[argc++] = blobmsg_data(cur);
380 if (argc == max - 1)
381 return false;
382 }
383
384 out:
385 argv[argc] = NULL;
386 return true;
387 }
388
389 static int
390 proto_shell_run_command(struct proto_shell_state *state, struct blob_attr **tb)
391 {
392 char *argv[64];
393 char *env[32];
394
395 if (!tb[NOTIFY_COMMAND])
396 goto error;
397
398 if (!fill_string_list(tb[NOTIFY_COMMAND], argv, ARRAY_SIZE(argv)))
399 goto error;
400
401 if (!fill_string_list(tb[NOTIFY_ENV], env, ARRAY_SIZE(env)))
402 goto error;
403
404 netifd_start_process((const char **) argv, (char **) env, &state->proto_task);
405
406 return 0;
407
408 error:
409 return UBUS_STATUS_INVALID_ARGUMENT;
410 }
411
412 static int
413 proto_shell_kill_command(struct proto_shell_state *state, struct blob_attr **tb)
414 {
415 unsigned int signal = ~0;
416
417 if (tb[NOTIFY_SIGNAL])
418 signal = blobmsg_get_u32(tb[NOTIFY_SIGNAL]);
419
420 if (signal > 31)
421 signal = SIGTERM;
422
423 if (state->proto_task.uloop.pending) {
424 kill(state->proto_task.uloop.pid, signal);
425 state->teardown_wait_task = true;
426 }
427
428 return 0;
429 }
430
431 static int
432 proto_shell_notify(struct interface_proto_state *proto, struct blob_attr *attr)
433 {
434 struct proto_shell_state *state;
435 struct blob_attr *tb[__NOTIFY_LAST];
436
437 state = container_of(proto, struct proto_shell_state, proto);
438
439 blobmsg_parse(notify_attr, __NOTIFY_LAST, tb, blob_data(attr), blob_len(attr));
440 if (!tb[NOTIFY_ACTION])
441 return UBUS_STATUS_INVALID_ARGUMENT;
442
443 switch(blobmsg_get_u32(tb[NOTIFY_ACTION])) {
444 case 0:
445 return proto_shell_update_link(state, tb);
446 case 1:
447 return proto_shell_run_command(state, tb);
448 case 2:
449 return proto_shell_kill_command(state, tb);
450 default:
451 return UBUS_STATUS_INVALID_ARGUMENT;
452 }
453 }
454
455 struct interface_proto_state *
456 proto_shell_attach(const struct proto_handler *h, struct interface *iface,
457 struct blob_attr *attr)
458 {
459 struct proto_shell_state *state;
460
461 state = calloc(1, sizeof(*state));
462 state->config = malloc(blob_pad_len(attr));
463 if (!state->config)
464 goto error;
465
466 memcpy(state->config, attr, blob_pad_len(attr));
467 state->proto.free = proto_shell_free;
468 state->proto.notify = proto_shell_notify;
469 state->proto.cb = proto_shell_handler;
470 state->setup_timeout.cb = proto_shell_setup_timeout_cb;
471 state->setup_task.cb = proto_shell_setup_cb;
472 state->setup_task.dir_fd = proto_fd.fd;
473 state->setup_task.log_prefix = iface->name;
474 state->teardown_task.cb = proto_shell_teardown_cb;
475 state->teardown_task.dir_fd = proto_fd.fd;
476 state->teardown_task.log_prefix = iface->name;
477 state->proto_task.cb = proto_shell_task_cb;
478 state->proto_task.dir_fd = proto_fd.fd;
479 state->proto_task.log_prefix = iface->name;
480 state->handler = container_of(h, struct proto_shell_handler, proto);
481
482 return &state->proto;
483
484 error:
485 free(state);
486 return NULL;
487 }
488
489 static json_object *
490 check_type(json_object *obj, json_type type)
491 {
492 if (!obj)
493 return NULL;
494
495 if (json_object_get_type(obj) != type)
496 return NULL;
497
498 return obj;
499 }
500
501 static inline json_object *
502 get_field(json_object *obj, const char *name, json_type type)
503 {
504 return check_type(json_object_object_get(obj, name), type);
505 }
506
507 static char *
508 proto_shell_parse_config(struct config_param_list *config, json_object *obj)
509 {
510 struct blobmsg_policy *attrs;
511 char *str_buf, *str_cur;
512 int str_len = 0;
513 int i;
514
515 config->n_params = json_object_array_length(obj);
516 attrs = calloc(1, sizeof(*attrs) * config->n_params);
517 if (!attrs)
518 return NULL;
519
520 config->params = attrs;
521 for (i = 0; i < config->n_params; i++) {
522 json_object *cur, *name, *type;
523
524 cur = check_type(json_object_array_get_idx(obj, i), json_type_array);
525 if (!cur)
526 goto error;
527
528 name = check_type(json_object_array_get_idx(cur, 0), json_type_string);
529 if (!name)
530 goto error;
531
532 type = check_type(json_object_array_get_idx(cur, 1), json_type_int);
533 if (!type)
534 goto error;
535
536 attrs[i].name = json_object_get_string(name);
537 attrs[i].type = json_object_get_int(type);
538 if (attrs[i].type > BLOBMSG_TYPE_LAST)
539 goto error;
540
541 str_len += strlen(attrs[i].name) + 1;
542 }
543
544 str_buf = malloc(str_len);
545 if (!str_buf)
546 goto error;
547
548 str_cur = str_buf;
549 for (i = 0; i < config->n_params; i++) {
550 const char *name = attrs[i].name;
551
552 attrs[i].name = str_cur;
553 str_cur += sprintf(str_cur, "%s", name) + 1;
554 }
555
556 return str_buf;
557
558 error:
559 free(attrs);
560 config->n_params = 0;
561 return NULL;
562 }
563
564 static void
565 proto_shell_add_handler(const char *script, json_object *obj)
566 {
567 struct proto_shell_handler *handler;
568 struct proto_handler *proto;
569 json_object *config, *tmp;
570 const char *name;
571 char *str;
572
573 if (!check_type(obj, json_type_object))
574 return;
575
576 tmp = get_field(obj, "name", json_type_string);
577 if (!tmp)
578 return;
579
580 name = json_object_get_string(tmp);
581
582 handler = calloc(1, sizeof(*handler) +
583 strlen(script) + 1 +
584 strlen(name) + 1);
585 if (!handler)
586 return;
587
588 strcpy(handler->script_name, script);
589
590 str = handler->script_name + strlen(handler->script_name) + 1;
591 strcpy(str, name);
592
593 proto = &handler->proto;
594 proto->name = str;
595 proto->config_params = &handler->config;
596 proto->attach = proto_shell_attach;
597
598 tmp = get_field(obj, "no-device", json_type_boolean);
599 if (tmp && json_object_get_boolean(tmp))
600 handler->proto.flags |= PROTO_FLAG_NODEV;
601
602 config = get_field(obj, "config", json_type_array);
603 if (config)
604 handler->config_buf = proto_shell_parse_config(&handler->config, config);
605
606 DPRINTF("Add handler for script %s: %s\n", script, proto->name);
607 add_proto_handler(proto);
608 }
609
610 static void proto_shell_add_script(const char *name)
611 {
612 struct json_tokener *tok = NULL;
613 json_object *obj;
614 static char buf[512];
615 char *start, *end, *cmd;
616 FILE *f;
617 int buflen, len;
618
619 #define DUMP_SUFFIX " '' dump"
620
621 cmd = alloca(strlen(name) + 1 + sizeof(DUMP_SUFFIX));
622 sprintf(cmd, "%s" DUMP_SUFFIX, name);
623
624 f = popen(cmd, "r");
625 if (!f)
626 return;
627
628 do {
629 buflen = fread(buf, 1, sizeof(buf) - 1, f);
630 if (buflen <= 0)
631 continue;
632
633 start = buf;
634 len = buflen;
635 do {
636 end = memchr(start, '\n', len);
637 if (end)
638 len = end - start;
639
640 if (!tok)
641 tok = json_tokener_new();
642
643 obj = json_tokener_parse_ex(tok, start, len);
644 if (!is_error(obj)) {
645 proto_shell_add_handler(name, obj);
646 json_object_put(obj);
647 json_tokener_free(tok);
648 tok = NULL;
649 }
650
651 if (end) {
652 start = end + 1;
653 len = buflen - (start - buf);
654 }
655 } while (len > 0);
656 } while (!feof(f) && !ferror(f));
657
658 if (tok)
659 json_tokener_free(tok);
660
661 pclose(f);
662 }
663
664 void __init proto_shell_init(void)
665 {
666 glob_t g;
667 int main_fd;
668 int i;
669
670 main_fd = open(".", O_RDONLY | O_DIRECTORY);
671 if (main_fd < 0)
672 return;
673
674 if (chdir(main_path)) {
675 perror("chdir(main path)");
676 goto close_cur;
677 }
678
679 if (chdir("./proto"))
680 goto close_cur;
681
682 proto_fd.fd = open(".", O_RDONLY | O_DIRECTORY);
683 if (proto_fd.fd < 0)
684 goto close_cur;
685
686 netifd_fd_add(&proto_fd);
687 glob("./*.sh", 0, NULL, &g);
688 for (i = 0; i < g.gl_pathc; i++)
689 proto_shell_add_script(g.gl_pathv[i]);
690
691 close_cur:
692 fchdir(main_fd);
693 close(main_fd);
694 }