1/*****************************************************************************\
2*  _  _       _          _              ___                                   *
3* | || | ___ | |_  _ __ | | _  _  __ _ |_  )                                  *
4* | __ |/ _ \|  _|| '_ \| || || |/ _` | / /                                   *
5* |_||_|\___/ \__|| .__/|_| \_,_|\__, |/___|                                  *
6*                 |_|            |___/                                        *
7\*****************************************************************************/
8
9#include <fcntl.h>
10#include <stdio.h>
11#include <unistd.h>
12#include <regex.h>
13#include <ctype.h>
14#include <stdlib.h>
15#include <string.h>
16#include <errno.h>
17#include <libgen.h>
18#include <pwd.h>
19#include <grp.h>
20#include <sys/wait.h>
21#include <sys/types.h>
22#include <sys/stat.h>
23
24#include "mem_utils.h"
25#include "hotplug2.h"
26#include "rules.h"
27
28#define last_rule return_rules->rules[return_rules->rules_c - 1]
29
30static void mkdir_p(char *path) {
31	char *ptr;
32	struct stat statbuf;
33
34	path = strdup(path);
35	path = dirname(path);
36	stat(path, &statbuf);
37	/* All is well... */
38	if (S_ISDIR(statbuf.st_mode)) {
39		free(path);
40		return;
41	}
42
43	for (ptr = path; ptr != NULL; ptr = strchr(ptr, '/')) {
44		if (ptr == path) {
45			ptr++;
46			continue;
47		}
48
49		errno = 0;
50		*ptr='\0';
51		mkdir(path, 0755);
52		*ptr='/';
53		if (errno != 0 && errno != EEXIST)
54			break;
55
56		ptr++;
57	}
58	mkdir(path, 0755);
59	free(path);
60}
61
62static char *replace_str(char *hay, char *needle, char *replacement) {
63        char *ptr, *start, *bptr, *buf;
64        int occurences, j;
65        size_t needle_len;
66        size_t replacement_len;
67        size_t haystack_len;
68
69	if (replacement == NULL || *replacement=='\0')
70		return hay;
71
72        if (needle == NULL || *needle=='\0')
73                return hay;
74
75        occurences = 0;
76        j = 0;
77        for (ptr = hay; *ptr != '\0'; ++ptr) {
78                if (needle[j] == *ptr) {
79                        ++j;
80                        if (needle[j] == '\0') {
81                                *(ptr-j+1) = '\0'; // mark occurence
82                                occurences++;
83                                j = 0;
84                        }
85                } else {
86			j=0;
87		}
88        }
89
90        if (occurences <= 0)
91                return hay;
92
93        haystack_len = (size_t)(ptr - hay);
94        replacement_len = strlen(replacement);
95        needle_len = strlen(needle);
96
97	buf = xmalloc(haystack_len + (replacement_len - needle_len) * occurences + 1);
98	start = hay;
99	ptr = hay;
100
101	bptr = buf;
102        while (occurences-- > 0) {
103                while (*ptr != '\0')
104                        ++ptr;
105
106		if (ptr-start > 0) {
107			memcpy(bptr, start, ptr - start);
108			bptr +=ptr - start;
109		}
110
111		memcpy(bptr, replacement, replacement_len);
112		bptr+=replacement_len;
113		ptr += needle_len;
114		start = ptr;
115	}
116
117	while (*ptr != '\0')
118		++ptr;
119
120	if (ptr-start > 0) {
121		memcpy(bptr, start, ptr - start);
122		bptr +=ptr - start;
123	}
124	*bptr='\0';
125
126	free(hay);
127
128        return buf;
129}
130
131inline int isescaped(char *hay, char *ptr) {
132	if (ptr <= hay)
133		return 0;
134
135	if (*(ptr-1) != '\\')
136		return 0;
137
138	return 1;
139}
140
141static char *replace_key_by_value(char *hay, struct hotplug2_event_t *event) {
142	char *sptr = hay, *ptr = hay;
143	char *buf, *replacement;
144
145	while ((sptr = strchr(sptr, '%')) != NULL) {
146		ptr = strchr(sptr+1, '%');
147		if (ptr != NULL) {
148			buf = xmalloc(ptr - sptr + 2);
149			buf[ptr - sptr + 1] = '\0';
150			memcpy(buf, sptr, ptr - sptr + 1);
151
152			buf[ptr - sptr] = '\0';
153			replacement = get_hotplug2_value_by_key(event, &buf[1]);
154			buf[ptr - sptr] = '%';
155
156			if (replacement != NULL) {
157				hay = replace_str(hay, buf, replacement);
158				sptr = hay;
159			} else {
160				sptr++;
161			}
162
163			free(buf);
164		} else {
165			sptr++;
166		}
167	}
168
169	hay = replace_str(hay, "%\\", "%");
170
171	return hay;
172}
173
174static int make_dev_from_event(struct hotplug2_event_t *event, char *path, mode_t devmode) {
175	char *subsystem, *major, *minor, *devpath;
176	int rv = 1;
177
178	major = get_hotplug2_value_by_key(event, "MAJOR");
179	if (major == NULL)
180		goto return_value;
181
182	minor = get_hotplug2_value_by_key(event, "MINOR");
183	if (minor == NULL)
184		goto return_value;
185
186	devpath = get_hotplug2_value_by_key(event, "DEVPATH");
187	if (devpath == NULL)
188		goto return_value;
189
190	subsystem = get_hotplug2_value_by_key(event, "SUBSYSTEM");
191	if (!strcmp(subsystem, "block"))
192		devmode |= S_IFBLK;
193	else
194		devmode |= S_IFCHR;
195
196	path = replace_key_by_value(path, event);
197	mkdir_p(path);
198	rv = mknod(path, devmode, makedev(atoi(major), atoi(minor)));
199	free(path);
200
201return_value:
202	return rv;
203}
204
205static int exec_noshell(struct hotplug2_event_t *event, char *application, char **argv) {
206	pid_t p;
207	int i, status;
208
209	p = fork();
210	switch (p) {
211		case -1:
212			return -1;
213		case 0:
214			for (i = 0; argv[i] != NULL; i++) {
215				argv[i] = replace_key_by_value(argv[i], event);
216			}
217			execvp(application, argv);
218			exit(0);
219			break;
220		default:
221			if (waitpid(p, &status, 0) == -1)
222				return -1;
223
224			return WEXITSTATUS(status);
225			break;
226	}
227}
228
229static int exec_shell(struct hotplug2_event_t *event, char *application) {
230	int rv;
231
232	application = replace_key_by_value(strdup(application), event);
233	rv = WEXITSTATUS(system(application));
234	free(application);
235	return rv;
236}
237
238static int make_symlink(struct hotplug2_event_t *event, char *target, char *linkname) {
239	int rv;
240
241	target = replace_key_by_value(strdup(target), event);
242	linkname = replace_key_by_value(strdup(linkname), event);
243
244	mkdir_p(linkname);
245	rv = symlink(target, linkname);
246
247	free(target);
248	free(linkname);
249
250	return rv;
251}
252
253static int chown_chgrp(int action, char *file, char *param) {
254	struct group *grp;
255	struct passwd *pwd;
256	int rv;
257
258	switch (action) {
259		case ACT_CHOWN:
260			pwd = getpwnam(param);
261			rv = chown(file, pwd->pw_uid, -1);
262			break;
263		case ACT_CHGRP:
264			grp = getgrnam(param);
265			rv = chown(file, -1, grp->gr_gid);
266			break;
267	}
268
269	return -1;
270}
271
272static int rule_condition_eval(struct hotplug2_event_t *event, struct condition_t *condition) {
273	int rv;
274	char *event_value = NULL;
275	regex_t preg;
276
277	event_value = get_hotplug2_value_by_key(event, condition->key);
278
279	switch (condition->type) {
280		case COND_MATCH_CMP:
281		case COND_NMATCH_CMP:
282			if (event_value == NULL)
283				return EVAL_NOT_AVAILABLE;
284
285			rv = strcmp(condition->value, event_value) ? EVAL_NOT_MATCH : EVAL_MATCH;
286			if (condition->type == COND_NMATCH_CMP)
287				rv = !rv;
288
289			return rv;
290
291		case COND_MATCH_RE:
292		case COND_NMATCH_RE:
293			if (event_value == NULL)
294				return EVAL_NOT_AVAILABLE;
295
296			regcomp(&preg, condition->value, REG_EXTENDED | REG_NOSUB);
297
298			rv = regexec(&preg, event_value, 0, NULL, 0) ? EVAL_NOT_MATCH : EVAL_MATCH;
299			if (condition->type == COND_NMATCH_RE)
300				rv = !rv;
301
302			regfree(&preg);
303
304			return rv;
305
306		case COND_MATCH_IS:
307			if (!strcasecmp(condition->value, "set"))
308				return event_value != NULL;
309
310			if (!strcasecmp(condition->value, "unset"))
311				return event_value == NULL;
312	}
313
314	return EVAL_NOT_AVAILABLE;
315}
316
317int rule_execute(struct hotplug2_event_t *event, struct rule_t *rule) {
318	int i, last_rv;
319
320	for (i = 0; i < rule->conditions_c; i++) {
321		if (rule_condition_eval(event, &(rule->conditions[i])) != EVAL_MATCH)
322			return 0;
323	}
324
325	last_rv = 0;
326
327	for (i = 0; i < event->env_vars_c; i++)
328		setenv(event->env_vars[i].key, event->env_vars[i].value, 1);
329
330	for (i = 0; i < rule->actions_c; i++) {
331		switch (rule->actions[i].type) {
332			case ACT_STOP_PROCESSING:
333				return 1;
334				break;
335			case ACT_STOP_IF_FAILED:
336				if (last_rv != 0)
337					return 1;
338				break;
339			case ACT_NEXT_EVENT:
340				return -1;
341				break;
342			case ACT_NEXT_IF_FAILED:
343				if (last_rv != 0)
344					return -1;
345				break;
346			case ACT_MAKE_DEVICE:
347				last_rv = make_dev_from_event(event, rule->actions[i].parameter[0], strtoul(rule->actions[i].parameter[1], NULL, 0));
348				break;
349			case ACT_CHMOD:
350				last_rv = chmod(rule->actions[i].parameter[0], strtoul(rule->actions[i].parameter[1], NULL, 0));
351				break;
352			case ACT_CHOWN:
353			case ACT_CHGRP:
354				last_rv = chown_chgrp(rule->actions[i].type, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
355				break;
356			case ACT_SYMLINK:
357				last_rv = make_symlink(event, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
358				break;
359			case ACT_RUN_SHELL:
360				last_rv = exec_shell(event, rule->actions[i].parameter[0]);
361				break;
362			case ACT_RUN_NOSHELL:
363				last_rv = exec_noshell(event, rule->actions[i].parameter[0], rule->actions[i].parameter);
364				break;
365			case ACT_SETENV:
366				last_rv = setenv(rule->actions[i].parameter[0], rule->actions[i].parameter[1], 1);
367				break;
368		}
369	}
370
371	return 0;
372}
373
374static inline int isinitiator(int c) {
375	switch (c) {
376		case ',':
377		case ';':
378		case '{':
379		case '}':
380			return 1;
381	}
382
383	return 0;
384}
385
386static inline void add_buffer(char **buf, int *blen, int *slen, char c) {
387	if (*slen + 1 >= *blen) {
388		*blen = *blen + 64;
389		*buf = xrealloc(*buf, *blen);
390	}
391
392	(*buf)[*slen] = c;
393	(*buf)[*slen+1] = '\0';
394	*slen += 1;
395}
396
397static char *rules_get_value(char *input, char **nptr) {
398	int quotes = QUOTES_NONE;
399	char *ptr = input;
400
401	int blen, slen;
402	char *buf;
403
404	blen = slen = 0;
405	buf = NULL;
406
407	if (isinitiator(*ptr)) {
408		add_buffer(&buf, &blen, &slen, *ptr);
409		ptr++;
410		goto return_value;
411	}
412
413	while (isspace(*ptr) && *ptr != '\0')
414		ptr++;
415
416	if (*ptr == '\0')
417		return NULL;
418
419	switch (*ptr) {
420		case '"':
421			quotes = QUOTES_DOUBLE;
422			ptr++;
423			break;
424		case '\'':
425			quotes = QUOTES_SINGLE;
426			ptr++;
427			break;
428	}
429
430	if (quotes != QUOTES_NONE) {
431		while (quotes != QUOTES_NONE) {
432			switch (*ptr) {
433				case '\\':
434					ptr++;
435					add_buffer(&buf, &blen, &slen, *ptr);
436					break;
437				case '"':
438					if (quotes == QUOTES_DOUBLE)
439						quotes = QUOTES_NONE;
440					break;
441				case '\'':
442					if (quotes == QUOTES_SINGLE)
443						quotes = QUOTES_NONE;
444					break;
445				default:
446					add_buffer(&buf, &blen, &slen, *ptr);
447					break;
448			}
449			ptr++;
450		}
451	} else {
452		while (!isspace(*ptr) && *ptr != '\0') {
453			if (isinitiator(*ptr))
454				break;
455
456			if (*ptr == '\\')
457				ptr++;
458
459			add_buffer(&buf, &blen, &slen, *ptr);
460			ptr++;
461		}
462	}
463
464return_value:
465	while (isspace(*ptr) && *ptr != '\0')
466		ptr++;
467
468	if (nptr != NULL)
469		*nptr = ptr;
470
471	return buf;
472}
473
474void rules_free(struct rules_t *rules) {
475	int i, j, k;
476
477	for (i = 0; i < rules->rules_c; i++) {
478		for (j = 0; j < rules->rules[i].actions_c; j++) {
479			if (rules->rules[i].actions[j].parameter != NULL) {
480				for (k = 0; rules->rules[i].actions[j].parameter[k] != NULL; k++)
481					free(rules->rules[i].actions[j].parameter[k]);
482				free(rules->rules[i].actions[j].parameter);
483			}
484		}
485		for (j = 0; j < rules->rules[i].conditions_c; j++) {
486			free(rules->rules[i].conditions[j].key);
487			free(rules->rules[i].conditions[j].value);
488		}
489		free(rules->rules[i].actions);
490		free(rules->rules[i].conditions);
491	}
492	free(rules->rules);
493}
494
495struct rules_t *rules_from_config(char *input) {
496	int status = STATUS_KEY, terminate;
497	char *buf;
498	struct rules_t *return_rules;
499
500	int i, j;
501	struct key_rec_t conditions[] = {	/*NOTE: We never have parameters for conditions. */
502		{"is", 0, COND_MATCH_IS},
503		{"==", 0, COND_MATCH_CMP},
504		{"!=", 0, COND_NMATCH_CMP},
505		{"~~", 0, COND_MATCH_RE},
506		{"!~", 0, COND_NMATCH_RE},
507		{NULL, 0, -1}
508	};
509	struct key_rec_t actions[] = {
510		/*one line / one command*/
511		{"run", 1, ACT_RUN_SHELL},
512		{"exec", -1, ACT_RUN_NOSHELL},
513		{"break", 0, ACT_STOP_PROCESSING},
514		{"break_if_failed", 0, ACT_STOP_IF_FAILED},
515		{"next", 0, ACT_NEXT_EVENT},
516		{"next_if_failed", 0, ACT_NEXT_IF_FAILED},
517		{"chown", 2, ACT_CHOWN},
518		{"chmod", 2, ACT_CHMOD},
519		{"chgrp", 2, ACT_CHGRP},
520		{"setenv", 2, ACT_SETENV},
521		/*symlink*/
522		{"symlink", 2, ACT_SYMLINK},
523		{"softlink", 2, ACT_SYMLINK},
524		/*makedev*/
525		{"mknod", 2, ACT_MAKE_DEVICE},
526		{"makedev", 2, ACT_MAKE_DEVICE},
527		{NULL, 0, -1}
528	};
529
530	return_rules = xmalloc(sizeof(struct rules_t));
531	return_rules->rules_c = 1;
532	return_rules->rules = xmalloc(sizeof(struct rule_t) * return_rules->rules_c);
533
534	last_rule.actions = NULL;
535	last_rule.actions_c = 0;
536	last_rule.conditions = NULL;
537	last_rule.conditions_c = 0;
538
539	terminate = 0;
540	do {
541		buf = rules_get_value(input, &input);
542		if (buf == NULL) {
543			ERROR("rules_get_value", "Malformed rule - unable to read!");
544			terminate = 1;
545			break;
546		}
547
548		if (buf[0] == '#') {
549			/* Skip to next line */
550			while (*input != '\0' && *input != '\n')
551				input++;
552			continue;
553		}
554
555		switch (status) {
556			case STATUS_KEY:
557				last_rule.conditions_c++;
558				last_rule.conditions = xrealloc(last_rule.conditions, sizeof(struct condition_t) * last_rule.conditions_c);
559				last_rule.conditions[last_rule.conditions_c-1].key = strdup(buf);
560
561				status = STATUS_CONDTYPE;
562				break;
563			case STATUS_CONDTYPE:
564				last_rule.conditions[last_rule.conditions_c-1].type = -1;
565
566				for (i = 0; conditions[i].key != NULL; i++) {
567					if (!strcmp(conditions[i].key, buf)) {
568						last_rule.conditions[last_rule.conditions_c-1].type = conditions[i].type;
569						break;
570					}
571				}
572
573				if (last_rule.conditions[last_rule.conditions_c-1].type == -1) {
574					ERROR("rules_get_value / status / condtype", "Malformed rule - unknown condition type.");
575					terminate = 1;
576				}
577
578				status = STATUS_VALUE;
579				break;
580			case STATUS_VALUE:
581				last_rule.conditions[last_rule.conditions_c-1].value = strdup(buf);
582
583				status = STATUS_INITIATOR;
584				break;
585			case STATUS_INITIATOR:
586				if (!strcmp(buf, ",") || !strcmp(buf, ";")) {
587					status = STATUS_KEY;
588				} else if (!strcmp(buf, "{")) {
589					status = STATUS_ACTION;
590				} else {
591					ERROR("rules_get_value / status / initiator", "Malformed rule - unknown initiator.");
592					terminate = 1;
593				}
594				break;
595			case STATUS_ACTION:
596				if (!strcmp(buf, "}")) {
597					status = STATUS_KEY;
598					return_rules->rules_c++;
599					return_rules->rules = xrealloc(return_rules->rules, sizeof(struct rule_t) * return_rules->rules_c);
600
601					last_rule.actions = NULL;
602					last_rule.actions_c = 0;
603					last_rule.conditions = NULL;
604					last_rule.conditions_c = 0;
605					break;
606				}
607
608				last_rule.actions_c++;
609				last_rule.actions = xrealloc(last_rule.actions, sizeof(struct action_t) * last_rule.actions_c);
610				last_rule.actions[last_rule.actions_c-1].parameter = NULL;
611				last_rule.actions[last_rule.actions_c-1].type = -1;
612
613				for (i = 0; actions[i].key != NULL; i++) {
614					if (!strcmp(actions[i].key, buf)) {
615						last_rule.actions[last_rule.actions_c-1].type = actions[i].type;
616						break;
617					}
618				}
619
620				if (last_rule.actions[last_rule.actions_c-1].type == -1) {
621					ERROR("rules_get_value / status / action", "Malformed rule - unknown action: %s.", buf);
622					terminate = 1;
623				}
624
625				if (actions[i].param > 0) {
626					last_rule.actions[last_rule.actions_c-1].parameter = xmalloc(sizeof(char*) * (actions[i].param + 1));
627					last_rule.actions[last_rule.actions_c-1].parameter[actions[i].param] = NULL;
628
629					for (j = 0; j < actions[i].param; j++) {
630						last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
631						if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], "}")) {
632							ERROR("rules_get_value / status / action", "Malformed rule - not enough parameters passed.");
633							status = STATUS_KEY;
634							break;
635						}
636						last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\}", "}");
637					}
638				} else if (actions[i].param == -1) {
639					j = 0;
640					last_rule.actions[last_rule.actions_c-1].parameter = xmalloc(sizeof(char*) * (j + 1));
641					last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
642					while (last_rule.actions[last_rule.actions_c-1].parameter[j] != NULL) {
643						if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], ";")) {
644							break;
645						}
646						if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], "}")) {
647							ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
648							status = STATUS_KEY;
649							break;
650						}
651						if (last_rule.actions[last_rule.actions_c-1].parameter[j][0] == '\0') {
652							ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
653							status = STATUS_KEY;
654							break;
655						}
656						last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\}", "}");
657						last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\;", ";");
658
659						j++;
660						last_rule.actions[last_rule.actions_c-1].parameter = xrealloc(last_rule.actions[last_rule.actions_c-1].parameter, sizeof(char*) * (j + 1));
661						last_rule.actions[last_rule.actions_c-1].parameter[j]  = rules_get_value(input, &input);
662					}
663					free(last_rule.actions[last_rule.actions_c-1].parameter[j]);
664					last_rule.actions[last_rule.actions_c-1].parameter[j] = NULL;
665				}
666
667				if (status == STATUS_KEY) {
668					return_rules->rules_c++;
669					return_rules->rules = xrealloc(return_rules->rules, sizeof(struct rule_t) * return_rules->rules_c);
670
671					last_rule.actions = NULL;
672					last_rule.actions_c = 0;
673					last_rule.conditions = NULL;
674					last_rule.conditions_c = 0;
675				}
676				break;
677		}
678
679		free(buf);
680	} while (*input != '\0' && !terminate);
681
682	if (!terminate) {
683		/* A little bit hacky cleanup */
684		return_rules->rules_c--;
685		return return_rules;
686	} else {
687		rules_free(return_rules);
688		free(return_rules);
689		return NULL;
690	}
691}
692