add validation for hotplug 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 rule_error(cur, "Unexpected element type");
311 return -1;
312 }
313
314 ret = __rule_process_type(cur, msg, expr, ARRAY_SIZE(expr), &found);
315 if (!found)
316 rule_error(cur, "Unknown expression type");
317
318 return ret;
319 }
320
321 static void cmd_add_string(const char *pattern, struct blob_attr *msg)
322 {
323 char *dest, *next, *str;
324 int len = 0;
325 bool var = false;
326
327 blobmsg_alloc_string_buffer(&b, NULL, 1);
328 str = alloca(strlen(pattern) + 1);
329 strcpy(str, pattern);
330 next = str;
331
332 while (*str) {
333 const char *cur;
334 int cur_len = 0;
335
336 next = strchr(str, '%');
337 if (!next)
338 next = str + strlen(str);
339
340 if (var) {
341 if (next > str) {
342 *next = 0;
343 cur = msg_find_var(msg, str);
344 if (cur)
345 cur_len = strlen(cur);
346 } else {
347 cur = str - 1;
348 cur_len = 1;
349 }
350 } else {
351 cur = str;
352 cur_len = next - str;
353 }
354
355 if (cur_len) {
356 dest = blobmsg_realloc_string_buffer(&b, cur_len);
357 memcpy(dest + len, cur, cur_len);
358 len += cur_len;
359 }
360
361 var = !var;
362 str = next + 1;
363 }
364
365 dest[len] = 0;
366 blobmsg_add_string_buffer(&b);
367 }
368
369 static int cmd_process_strings(struct blob_attr *attr, struct blob_attr *msg)
370 {
371 struct blob_attr *cur;
372 int args = -1;
373 int rem;
374 void *c;
375
376 blob_buf_init(&b, 0);
377 c = blobmsg_open_array(&b, NULL);
378 blobmsg_for_each_attr(cur, attr, rem) {
379 if (args++ < 0)
380 continue;
381
382 if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) {
383 rule_error(attr, "Invalid argument in command");
384 return -1;
385 }
386
387 cmd_add_string(blobmsg_data(cur), msg);
388 }
389
390 blobmsg_close_array(&b, c);
391
392 return 0;
393 }
394
395 static int __rule_process_cmd(struct blob_attr *cur, struct blob_attr *msg)
396 {
397 const char *name;
398 bool found;
399 int ret;
400
401 ret = __rule_process_type(cur, msg, cmd, ARRAY_SIZE(cmd), &found);
402 if (found)
403 return ret;
404
405 name = blobmsg_data(blobmsg_data(cur));
406 ret = cmd_process_strings(cur, msg);
407 if (ret)
408 return ret;
409
410 rule_handle_command(name, blob_data(b.head));
411
412 return 0;
413 }
414
415 static int rule_process_cmd(struct blob_attr *block, struct blob_attr *msg)
416 {
417 struct blob_attr *cur;
418 int rem;
419 int ret;
420 int i = 0;
421
422 if (blobmsg_type(block) != BLOBMSG_TYPE_ARRAY) {
423 rule_error(block, "Unexpected element type");
424 return -1;
425 }
426
427 blobmsg_for_each_attr(cur, block, rem) {
428 switch(blobmsg_type(cur)) {
429 case BLOBMSG_TYPE_STRING:
430 if (!i)
431 return __rule_process_cmd(block, msg);
432 default:
433 ret = rule_process_cmd(cur, msg);
434 if (ret < -1)
435 return ret;
436 break;
437 }
438 i++;
439 }
440
441 return 0;
442 }
443
444 void rule_process_msg(struct rule_file *f, struct blob_attr *msg)
445 {
446 rule_process_cmd(f->data, msg);
447 }
448
449 static struct rule_file *
450 rule_file_load(const char *filename)
451 {
452 struct rule_file *r;
453 struct stat st;
454
455 json_object *obj = NULL;
456
457 blob_buf_init(&b, 0);
458
459 if (stat(filename, &st))
460 return NULL;
461
462 obj = json_object_from_file((char *) filename);
463 if (!obj)
464 return NULL;
465
466 if (!json_object_is_type(obj, json_type_array)) {
467 json_object_put(obj);
468 return NULL;
469 }
470
471 blobmsg_add_json_element(&b, filename, obj);
472 json_object_put(obj);
473
474 r = calloc(1, sizeof(*r) + blob_len(b.head));
475 memcpy(r->data, blob_data(b.head), blob_len(b.head));
476 r->avl.key = blobmsg_name(r->data);
477
478 return r;
479 }
480
481 static struct avl_tree rule_files;
482
483 struct rule_file *
484 rule_file_get(const char *filename)
485 {
486 struct rule_file *r;
487
488 if (!rule_files.comp)
489 avl_init(&rule_files, avl_strcmp, false, NULL);
490
491 r = avl_find_element(&rule_files, filename, r, avl);
492 if (r)
493 return r;
494
495 r = rule_file_load(filename);
496 if (!r)
497 return NULL;
498
499 avl_insert(&rule_files, &r->avl);
500 return r;
501 }
502
503 void
504 rule_file_free_all(void)
505 {
506 struct rule_file *r, *next;
507
508 avl_remove_all_elements(&rule_files, r, avl, next)
509 free(r);
510
511 blob_buf_free(&b);
512 }