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