hotplug: add extra validation for commands
[project/procd.git] / hotplug-rule.c
1 #include <sys/stat.h>
2
3 #include <libgen.h>
4 #include <regex.h>
5
6 #include <json/json.h>
7 #include <libubox/avl-cmp.h>
8 #include <libubox/blobmsg_json.h>
9
10 #include "hotplug.h"
11
12 static struct blob_buf b;
13
14 static int rule_process_expr(struct blob_attr *cur, struct blob_attr *msg);
15 static int rule_process_cmd(struct blob_attr *cur, struct blob_attr *msg);
16
17 static char *__msg_find_var(struct blob_attr *msg, const char *name)
18 {
19 struct blob_attr *cur;
20 int rem;
21
22 blob_for_each_attr(cur, msg, rem) {
23 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
24 continue;
25
26 if (strcmp(blobmsg_name(cur), name) != 0)
27 continue;
28
29 return blobmsg_data(cur);
30 }
31
32 return NULL;
33 }
34
35 static char *msg_find_var(struct blob_attr *msg, const char *name)
36 {
37 char *str;
38
39 if (!strcmp(name, "DEVICENAME") || !strcmp(name, "DEVNAME")) {
40 str = __msg_find_var(msg, "DEVPATH");
41 if (!str)
42 return NULL;
43
44 return basename(str);
45 }
46
47 return __msg_find_var(msg, name);
48 }
49
50 static void
51 rule_get_tuple(struct blob_attr *cur, struct blob_attr **tb, int t1, int t2)
52 {
53 static struct blobmsg_policy expr_tuple[3] = {
54 { .type = BLOBMSG_TYPE_STRING },
55 {},
56 {},
57 };
58
59 expr_tuple[1].type = t1;
60 expr_tuple[2].type = t2;
61 blobmsg_parse_array(expr_tuple, 3, tb, blobmsg_data(cur), blobmsg_data_len(cur));
62 }
63
64 static int handle_if(struct blob_attr *expr, struct blob_attr *msg)
65 {
66 struct blob_attr *tb[4];
67 int ret;
68
69 static const struct blobmsg_policy if_tuple[4] = {
70 { .type = BLOBMSG_TYPE_STRING },
71 { .type = BLOBMSG_TYPE_ARRAY },
72 { .type = BLOBMSG_TYPE_ARRAY },
73 { .type = BLOBMSG_TYPE_ARRAY },
74 };
75
76 blobmsg_parse_array(if_tuple, 4, tb, blobmsg_data(expr), blobmsg_data_len(expr));
77
78 if (!tb[1] || !tb[2])
79 return 0;
80
81 ret = rule_process_expr(tb[1], msg);
82 if (ret < 0)
83 return 0;
84
85 if (ret)
86 return rule_process_cmd(tb[2], msg);
87
88 if (!tb[3])
89 return 0;
90
91 return rule_process_cmd(tb[3], msg);
92 }
93
94 static int handle_case(struct blob_attr *expr, struct blob_attr *msg)
95 {
96 struct blob_attr *tb[3], *cur;
97 const char *var;
98 int rem;
99
100 rule_get_tuple(expr, tb, BLOBMSG_TYPE_STRING, BLOBMSG_TYPE_TABLE);
101 if (!tb[1] || !tb[2])
102 return 0;
103
104 var = msg_find_var(msg, blobmsg_data(tb[1]));
105 if (!var)
106 return 0;
107
108 blobmsg_for_each_attr(cur, tb[2], rem) {
109 if (!strcmp(var, blobmsg_name(cur)))
110 return rule_process_cmd(cur, msg);
111 }
112
113 return 0;
114 }
115
116 static int handle_return(struct blob_attr *expr, struct blob_attr *msg)
117 {
118 return -2;
119 }
120
121 static int handle_include(struct blob_attr *expr, struct blob_attr *msg)
122 {
123 struct blob_attr *tb[3];
124 struct rule_file *r;
125
126 rule_get_tuple(expr, tb, BLOBMSG_TYPE_STRING, 0);
127 if (!tb[1])
128 return 0;
129
130 r = rule_file_get(blobmsg_data(tb[1]));
131 if (!r)
132 return 0;
133
134 return rule_process_cmd(r->data, msg);
135 }
136
137 static const struct rule_handler cmd[] = {
138 { "if", handle_if },
139 { "case", handle_case },
140 { "return", handle_return },
141 { "include", handle_include },
142 };
143
144 static int eq_regex_cmp(const char *str, const char *pattern, bool regex)
145 {
146 regex_t reg;
147 int ret;
148
149 if (!regex)
150 return !strcmp(str, pattern);
151
152 if (regcomp(&reg, pattern, REG_EXTENDED | REG_NOSUB))
153 return 0;
154
155 ret = !regexec(&reg, str, 0, NULL, 0);
156 regfree(&reg);
157
158 return ret;
159 }
160
161 static int expr_eq_regex(struct blob_attr *expr, struct blob_attr *msg, bool regex)
162 {
163 struct blob_attr *tb[3], *cur;
164 const char *var;
165 int rem;
166
167 rule_get_tuple(expr, tb, BLOBMSG_TYPE_STRING, 0);
168 if (!tb[1] || !tb[2])
169 return -1;
170
171 var = msg_find_var(msg, blobmsg_data(tb[1]));
172 if (!var)
173 return 0;
174
175 switch(blobmsg_type(tb[2])) {
176 case BLOBMSG_TYPE_STRING:
177 return eq_regex_cmp(var, blobmsg_data(tb[2]), regex);
178 case BLOBMSG_TYPE_ARRAY:
179 blobmsg_for_each_attr(cur, tb[2], rem) {
180 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) {
181 rule_error(cur, "Unexpected element type");
182 return -1;
183 }
184
185 if (eq_regex_cmp(var, blobmsg_data(cur), regex))
186 return 1;
187 }
188 return 0;
189 default:
190 rule_error(tb[2], "Unexpected element type");
191 return -1;
192 }
193 }
194
195 static int handle_expr_eq(struct blob_attr *expr, struct blob_attr *msg)
196 {
197 return expr_eq_regex(expr, msg, false);
198 }
199
200 static int handle_expr_regex(struct blob_attr *expr, struct blob_attr *msg)
201 {
202 return expr_eq_regex(expr, msg, true);
203 }
204
205 static int handle_expr_has(struct blob_attr *expr, struct blob_attr *msg)
206 {
207 struct blob_attr *tb[3], *cur;
208 int rem;
209
210 rule_get_tuple(expr, tb, 0, 0);
211 if (!tb[1])
212 return -1;
213
214 switch(blobmsg_type(tb[1])) {
215 case BLOBMSG_TYPE_STRING:
216 return !!msg_find_var(msg, blobmsg_data(tb[1]));
217 case BLOBMSG_TYPE_ARRAY:
218 blobmsg_for_each_attr(cur, tb[1], rem) {
219 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) {
220 rule_error(cur, "Unexpected element type");
221 return -1;
222 }
223
224 if (msg_find_var(msg, blobmsg_data(cur)))
225 return 1;
226 }
227 return 0;
228 default:
229 rule_error(tb[1], "Unexpected element type");
230 return -1;
231 }
232 }
233
234 static int expr_and_or(struct blob_attr *expr, struct blob_attr *msg, bool and)
235 {
236 struct blob_attr *cur;
237 int ret, rem;
238 int i = 0;
239
240 blobmsg_for_each_attr(cur, expr, rem) {
241 if (i++ < 1)
242 continue;
243
244 ret = rule_process_expr(cur, msg);
245 if (ret < 0)
246 return ret;
247
248 if (ret != and)
249 return ret;
250 }
251
252 return and;
253 }
254
255 static int handle_expr_and(struct blob_attr *expr, struct blob_attr *msg)
256 {
257 return expr_and_or(expr, msg, 1);
258 }
259
260 static int handle_expr_or(struct blob_attr *expr, struct blob_attr *msg)
261 {
262 return expr_and_or(expr, msg, 0);
263 }
264
265 static int handle_expr_not(struct blob_attr *expr, struct blob_attr *msg)
266 {
267 struct blob_attr *tb[3];
268
269 rule_get_tuple(expr, tb, BLOBMSG_TYPE_ARRAY, 0);
270 if (!tb[1])
271 return -1;
272
273 return rule_process_expr(tb[1], msg);
274 }
275
276 static const struct rule_handler expr[] = {
277 { "eq", handle_expr_eq },
278 { "regex", handle_expr_regex },
279 { "has", handle_expr_has },
280 { "and", handle_expr_and },
281 { "or", handle_expr_or },
282 { "not", handle_expr_not },
283 };
284
285 static int
286 __rule_process_type(struct blob_attr *cur, struct blob_attr *msg,
287 const struct rule_handler *h, int n, bool *found)
288 {
289 const char *name = blobmsg_data(blobmsg_data(cur));
290 int i;
291
292 for (i = 0; i < n; i++) {
293 if (strcmp(name, h[i].name) != 0)
294 continue;
295
296 *found = true;
297 return h[i].handler(cur, msg);
298 }
299
300 *found = false;
301 return -1;
302 }
303
304 static int rule_process_expr(struct blob_attr *cur, struct blob_attr *msg)
305 {
306 bool found;
307 int ret;
308
309 if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY ||
310 blobmsg_type(blobmsg_data(cur)) != BLOBMSG_TYPE_STRING) {
311 rule_error(cur, "Unexpected element type");
312 return -1;
313 }
314
315 ret = __rule_process_type(cur, msg, expr, ARRAY_SIZE(expr), &found);
316 if (!found)
317 rule_error(cur, "Unknown expression type");
318
319 return ret;
320 }
321
322 static void cmd_add_string(const char *pattern, struct blob_attr *msg)
323 {
324 char *dest, *next, *str;
325 int len = 0;
326 bool var = false;
327
328 blobmsg_alloc_string_buffer(&b, NULL, 1);
329 str = alloca(strlen(pattern) + 1);
330 strcpy(str, pattern);
331 next = str;
332
333 while (*str) {
334 const char *cur;
335 int cur_len = 0;
336
337 next = strchr(str, '%');
338 if (!next)
339 next = str + strlen(str);
340
341 if (var) {
342 if (next > str) {
343 *next = 0;
344 cur = msg_find_var(msg, str);
345 if (cur)
346 cur_len = strlen(cur);
347 } else {
348 cur = str - 1;
349 cur_len = 1;
350 }
351 } else {
352 cur = str;
353 cur_len = next - str;
354 }
355
356 if (cur_len) {
357 dest = blobmsg_realloc_string_buffer(&b, cur_len);
358 memcpy(dest + len, cur, cur_len);
359 len += cur_len;
360 }
361
362 var = !var;
363 str = next + 1;
364 }
365
366 dest[len] = 0;
367 blobmsg_add_string_buffer(&b);
368 }
369
370 static int cmd_process_strings(struct blob_attr *attr, struct blob_attr *msg)
371 {
372 struct blob_attr *cur;
373 int args = -1;
374 int rem;
375 void *c;
376
377 blob_buf_init(&b, 0);
378 c = blobmsg_open_array(&b, NULL);
379 blobmsg_for_each_attr(cur, attr, rem) {
380 if (args++ < 0)
381 continue;
382
383 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) {
384 rule_error(attr, "Invalid argument in command");
385 return -1;
386 }
387
388 cmd_add_string(blobmsg_data(cur), msg);
389 }
390
391 blobmsg_close_array(&b, c);
392
393 return 0;
394 }
395
396 static int __rule_process_cmd(struct blob_attr *cur, struct blob_attr *msg)
397 {
398 const char *name;
399 bool found;
400 int ret;
401
402 if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY ||
403 blobmsg_type(blobmsg_data(cur)) != BLOBMSG_TYPE_STRING) {
404 rule_error(cur, "Unexpected element type");
405 return -1;
406 }
407
408 ret = __rule_process_type(cur, msg, cmd, ARRAY_SIZE(cmd), &found);
409 if (found)
410 return ret;
411
412 name = blobmsg_data(blobmsg_data(cur));
413 ret = cmd_process_strings(cur, msg);
414 if (ret)
415 return ret;
416
417 rule_handle_command(name, blob_data(b.head));
418
419 return 0;
420 }
421
422 static int rule_process_cmd(struct blob_attr *block, struct blob_attr *msg)
423 {
424 struct blob_attr *cur;
425 int rem;
426 int ret;
427 int i = 0;
428
429 if (blobmsg_type(block) != BLOBMSG_TYPE_ARRAY) {
430 rule_error(block, "Unexpected element type");
431 return -1;
432 }
433
434 blobmsg_for_each_attr(cur, block, rem) {
435 switch(blobmsg_type(cur)) {
436 case BLOBMSG_TYPE_STRING:
437 if (!i)
438 return __rule_process_cmd(block, msg);
439 default:
440 ret = rule_process_cmd(cur, msg);
441 if (ret < -1)
442 return ret;
443 break;
444 }
445 i++;
446 }
447
448 return 0;
449 }
450
451 void rule_process_msg(struct rule_file *f, struct blob_attr *msg)
452 {
453 rule_process_cmd(f->data, msg);
454 }
455
456 static struct rule_file *
457 rule_file_load(const char *filename)
458 {
459 struct rule_file *r;
460 struct stat st;
461
462 json_object *obj = NULL;
463
464 blob_buf_init(&b, 0);
465
466 if (stat(filename, &st))
467 return NULL;
468
469 obj = json_object_from_file((char *) filename);
470 if (!obj)
471 return NULL;
472
473 if (!json_object_is_type(obj, json_type_array)) {
474 json_object_put(obj);
475 return NULL;
476 }
477
478 blobmsg_add_json_element(&b, filename, obj);
479 json_object_put(obj);
480
481 r = calloc(1, sizeof(*r) + blob_len(b.head));
482 memcpy(r->data, blob_data(b.head), blob_len(b.head));
483 r->avl.key = blobmsg_name(r->data);
484
485 return r;
486 }
487
488 static struct avl_tree rule_files;
489
490 struct rule_file *
491 rule_file_get(const char *filename)
492 {
493 struct rule_file *r;
494
495 if (!rule_files.comp)
496 avl_init(&rule_files, avl_strcmp, false, NULL);
497
498 r = avl_find_element(&rule_files, filename, r, avl);
499 if (r)
500 return r;
501
502 r = rule_file_load(filename);
503 if (!r)
504 return NULL;
505
506 avl_insert(&rule_files, &r->avl);
507 return r;
508 }
509
510 void
511 rule_file_free_all(void)
512 {
513 struct rule_file *r, *next;
514
515 avl_remove_all_elements(&rule_files, r, avl, next)
516 free(r);
517
518 blob_buf_free(&b);
519 }