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