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 "filemap_utils.h"
26#include "hotplug2.h"
27#include "rules.h"
28
29
30/**
31 * Function supplementing 'mkdir -p'.
32 *
33 * @1 Path to be mkdir'd
34 *
35 * Returns: void
36 */
37static void mkdir_p(char *path) {
38	char *ptr;
39	struct stat statbuf;
40
41	path = strdup(path);
42	path = dirname(path);
43	stat(path, &statbuf);
44	/* All is well... */
45	if (S_ISDIR(statbuf.st_mode)) {
46		free(path);
47		return;
48	}
49
50	for (ptr = path; ptr != NULL; ptr = strchr(ptr, '/')) {
51		if (ptr == path) {
52			ptr++;
53			continue;
54		}
55
56		errno = 0;
57		*ptr='\0';
58		mkdir(path, 0755);
59		*ptr='/';
60		if (errno != 0 && errno != EEXIST)
61			break;
62
63		ptr++;
64	}
65	mkdir(path, 0755);
66	free(path);
67}
68
69/**
70 * Function supplementing 'rmdir -p'.
71 *
72 * @1 Path to be rmdir'd
73 *
74 * Returns: void
75 */
76static void rmdir_p(char *path) {
77	char *ptr;
78
79	path = strdup(path);
80	ptr = path;
81	while (ptr != NULL) {
82		ptr = strrchr(path, '/');
83		if (ptr == NULL)
84			break;
85
86		*ptr = '\0';
87
88		if (rmdir(path))
89			break;
90	}
91	free(path);
92}
93
94/**
95 * Replaces all needles by a given value.
96 *
97 * @1 Haystack (which gets free'd in the function)
98 * @2 Needle
99 * @3 Needle replacement
100 *
101 * Returns: Newly allocated haysteck after replacement.
102 */
103static char *replace_str(char *hay, char *needle, char *replacement) {
104        char *ptr, *start, *bptr, *buf;
105        int occurences, j;
106        size_t needle_len;
107        size_t replacement_len;
108        size_t haystack_len;
109
110	if (replacement == NULL || *replacement=='\0')
111		return hay;
112
113        if (needle == NULL || *needle=='\0')
114                return hay;
115
116        occurences = 0;
117        j = 0;
118        for (ptr = hay; *ptr != '\0'; ++ptr) {
119                if (needle[j] == *ptr) {
120                        ++j;
121                        if (needle[j] == '\0') {
122                                *(ptr-j+1) = '\0'; // mark occurence
123                                occurences++;
124                                j = 0;
125                        }
126                } else {
127			j=0;
128		}
129        }
130
131        if (occurences <= 0)
132                return hay;
133
134        haystack_len = (size_t)(ptr - hay);
135        replacement_len = strlen(replacement);
136        needle_len = strlen(needle);
137
138	buf = xmalloc(haystack_len + (replacement_len - needle_len) * occurences + 1);
139	start = hay;
140	ptr = hay;
141
142	bptr = buf;
143        while (occurences-- > 0) {
144                while (*ptr != '\0')
145                        ++ptr;
146
147		if (ptr-start > 0) {
148			memcpy(bptr, start, ptr - start);
149			bptr +=ptr - start;
150		}
151
152		memcpy(bptr, replacement, replacement_len);
153		bptr+=replacement_len;
154		ptr += needle_len;
155		start = ptr;
156	}
157
158	while (*ptr != '\0')
159		++ptr;
160
161	if (ptr-start > 0) {
162		memcpy(bptr, start, ptr - start);
163		bptr +=ptr - start;
164	}
165	*bptr='\0';
166
167	free(hay);
168
169        return buf;
170}
171
172/**
173 * Trivial utility, figuring out whether a character is escaped or not.
174 *
175 * @1 Haystack
176 * @2 Pointer to the character in question
177 *
178 * Returns: 1 if escaped, 0 otherwise
179 */
180static inline int isescaped(char *hay, char *ptr) {
181	if (ptr <= hay)
182		return 0;
183
184	if (*(ptr-1) != '\\')
185		return 0;
186
187	return 1;
188}
189
190/**
191 * Performs replacement of all keys by their value based on the hotplug
192 * event structure. Keys are identified as strings %KEY%.
193 *
194 * @1 Haystack
195 * @2 Hotplug event structure
196 *
197 * Returns: Newly allocated haystack (old is freed)
198 */
199static char *replace_key_by_value(char *hay, struct hotplug2_event_t *event) {
200	char *sptr = hay, *ptr = hay;
201	char *buf, *replacement;
202
203	while ((sptr = strchr(sptr, '%')) != NULL) {
204		ptr = strchr(sptr+1, '%');
205		if (ptr != NULL) {
206			buf = xmalloc(ptr - sptr + 2);
207			buf[ptr - sptr + 1] = '\0';
208			memcpy(buf, sptr, ptr - sptr + 1);
209
210			buf[ptr - sptr] = '\0';
211			replacement = get_hotplug2_value_by_key(event, &buf[1]);
212			buf[ptr - sptr] = '%';
213
214			if (replacement != NULL) {
215				hay = replace_str(hay, buf, replacement);
216				sptr = hay;
217			} else {
218				sptr++;
219			}
220
221			free(buf);
222		} else {
223			sptr++;
224		}
225	}
226
227	hay = replace_str(hay, "%\\", "%");
228
229	return hay;
230}
231
232/**
233 * Obtains all information from hotplug event structure about a device node.
234 * Creates the device node at a given path (expandable by keys) and with
235 * given mode.
236 *
237 * @1 Hotplug event structure
238 * @2 Path (may contain keys)
239 * @3 Mode of the file
240 *
241 * Returns: 0 if success, non-zero otherwise
242 */
243static int make_dev_from_event(struct hotplug2_event_t *event, char *path, mode_t devmode) {
244	char *subsystem, *major, *minor, *devpath;
245	int rv = 1;
246
247	major = get_hotplug2_value_by_key(event, "MAJOR");
248	if (major == NULL)
249		goto return_value;
250
251	minor = get_hotplug2_value_by_key(event, "MINOR");
252	if (minor == NULL)
253		goto return_value;
254
255	devpath = get_hotplug2_value_by_key(event, "DEVPATH");
256	if (devpath == NULL)
257		goto return_value;
258
259	subsystem = get_hotplug2_value_by_key(event, "SUBSYSTEM");
260	if (!strcmp(subsystem, "block"))
261		devmode |= S_IFBLK;
262	else
263		devmode |= S_IFCHR;
264
265	path = replace_key_by_value(path, event);
266	mkdir_p(path);
267	rv = mknod(path, devmode, makedev(atoi(major), atoi(minor)));
268
269	/*
270	 * Fixes an issue caused by devmode being modified by umask.
271	 */
272	chmod(path, devmode);
273
274	free(path);
275
276return_value:
277	return rv;
278}
279
280/**
281 * Execute an application without invoking a shell.
282 *
283 * @1 Hotplug event structure
284 * @2 Path to the application, with expandable keys
285 * @3 Argv for the application, with expandable keys
286 *
287 * Returns: Exit status of the application.
288 */
289static int exec_noshell(struct hotplug2_event_t *event, char *application, char **argv) {
290	pid_t p;
291	int i, status;
292
293	p = fork();
294	switch (p) {
295		case -1:
296			return -1;
297		case 0:
298			application = replace_key_by_value(strdup(application), event);
299			for (i = 0; argv[i] != NULL; i++) {
300				argv[i] = replace_key_by_value(argv[i], event);
301			}
302			execvp(application, argv);
303			exit(127);
304			break;
305		default:
306			if (waitpid(p, &status, 0) == -1)
307				return -1;
308
309			return WEXITSTATUS(status);
310			break;
311	}
312}
313
314/**
315 * Execute an application while invoking a shell.
316 *
317 * @1 Hotplug event structure
318 * @2 The application and all its arguments, with expandable keys
319 *
320 * Returns: Exit status of the application.
321 */
322static int exec_shell(struct hotplug2_event_t *event, char *application) {
323	int rv;
324
325	application = replace_key_by_value(strdup(application), event);
326	rv = WEXITSTATUS(system(application));
327	free(application);
328	return rv;
329}
330
331/**
332 * Create a symlink, with necessary parent directories.
333 *
334 * @1 Hotplug event structure
335 * @2 Link target, with expandable keys
336 * @3 Link name, with expandable keys
337 *
338 * Returns: return value of symlink()
339 */
340static int make_symlink(struct hotplug2_event_t *event, char *target, char *linkname) {
341	int rv;
342
343	target = replace_key_by_value(strdup(target), event);
344	linkname = replace_key_by_value(strdup(linkname), event);
345
346	mkdir_p(linkname);
347	rv = symlink(target, linkname);
348
349	free(target);
350	free(linkname);
351
352	return rv;
353}
354
355/**
356 * Chmod a given file.
357 *
358 * @1 Hotplug event structure
359 * @2 File name, with expandable keys
360 * @3 Chmod value, with expandable keys
361 *
362 * Returns: return value of chmod()
363 */
364static int chmod_file(struct hotplug2_event_t *event, char *file, char *value) {
365	int rv;
366
367	file = replace_key_by_value(strdup(file), event);
368	value = replace_key_by_value(strdup(value), event);
369
370	rv = chmod(file, strtoul(value, 0, 8));
371
372	free(file);
373	free(value);
374
375	return rv;
376}
377
378
379/**
380 * Change owner or group of a given file.
381 *
382 * @1 Hotplug event structure
383 * @2 Whether we chown or chgrp
384 * @3 Filename, with expandable keys
385 * @4 Group or user name, with expandable keys
386 *
387 * Returns: return value of chown()
388 */
389static int chown_chgrp(struct hotplug2_event_t *event, int action, char *file, char *param) {
390	struct group *grp;
391	struct passwd *pwd;
392	int rv;
393
394	file = replace_key_by_value(strdup(file), event);
395	param = replace_key_by_value(strdup(param), event);
396
397	rv = -1;
398
399	switch (action) {
400		case ACT_CHOWN:
401			pwd = getpwnam(param);
402			rv = chown(file, pwd->pw_uid, -1);
403			break;
404		case ACT_CHGRP:
405			grp = getgrnam(param);
406			rv = chown(file, -1, grp->gr_gid);
407			break;
408	}
409
410	free(file);
411	free(param);
412
413	return rv;
414}
415
416/**
417 * Prints all uevent keys.
418 *
419 * @1 Hotplug event structure
420 *
421 * Returns: void
422 */
423static void print_debug(struct hotplug2_event_t *event) {
424	int i;
425
426	for (i = 0; i < event->env_vars_c; i++)
427		printf("%s=%s\n", event->env_vars[i].key, event->env_vars[i].value);
428}
429
430/**
431 * Evaluates a condition according to a given hotplug event structure.
432 *
433 * @1 Hotplug event structure
434 * @2 Condition to be evaluated
435 *
436 * Returns: 1 if match, 0 if no match, EVAL_NOT_AVAILABLE if unable to
437 * perform evaluation
438 */
439int rule_condition_eval(struct hotplug2_event_t *event, struct condition_t *condition) {
440	int rv;
441	char *event_value = NULL;
442	regex_t preg;
443
444	event_value = get_hotplug2_value_by_key(event, condition->key);
445
446	switch (condition->type) {
447		case COND_MATCH_CMP:
448		case COND_NMATCH_CMP:
449			if (event_value == NULL)
450				return EVAL_NOT_AVAILABLE;
451
452			rv = strcmp(condition->value, event_value) ? EVAL_NOT_MATCH : EVAL_MATCH;
453			if (condition->type == COND_NMATCH_CMP)
454				rv = !rv;
455
456			return rv;
457
458		case COND_MATCH_RE:
459		case COND_NMATCH_RE:
460			if (event_value == NULL)
461				return EVAL_NOT_AVAILABLE;
462
463			regcomp(&preg, condition->value, REG_EXTENDED | REG_NOSUB);
464
465			rv = regexec(&preg, event_value, 0, NULL, 0) ? EVAL_NOT_MATCH : EVAL_MATCH;
466			if (condition->type == COND_NMATCH_RE)
467				rv = !rv;
468
469			regfree(&preg);
470
471			return rv;
472
473		case COND_MATCH_IS:
474			if (!strcasecmp(condition->value, "set"))
475				return event_value != NULL;
476
477			if (!strcasecmp(condition->value, "unset"))
478				return event_value == NULL;
479	}
480
481	return EVAL_NOT_AVAILABLE;
482}
483
484/**
485 * Creates a "key=value" string from the given key and value
486 *
487 * @1 Key
488 * @2 Value
489 *
490 * Returns: Newly allocated string in "key=value" form
491 *
492 */
493static char* alloc_env(const char *key, const char *value) {
494	size_t keylen, vallen;
495	char *combined;
496
497	keylen = strlen(key);
498	vallen = strlen(value) + 1;
499
500	combined = xmalloc(keylen + vallen + 1);
501	memcpy(combined, key, keylen);
502	combined[keylen] = '=';
503	memcpy(&combined[keylen + 1], value, vallen);
504
505	return combined;
506}
507
508/**
509 * Executes a rule. Contains evaluation of all conditions prior
510 * to execution.
511 *
512 * @1 Hotplug event structure
513 * @2 The rule to be executed
514 *
515 * Returns: 0 if success, -1 if the whole event is to be
516 * discared, 1 if bail out of this particular rule was required
517 */
518int rule_execute(struct hotplug2_event_t *event, struct rule_t *rule) {
519	int i, last_rv, res;
520	char **env;
521
522	for (i = 0; i < rule->conditions_c; i++) {
523		if (rule_condition_eval(event, &(rule->conditions[i])) != EVAL_MATCH)
524			return 0;
525	}
526
527	res = 0;
528	last_rv = 0;
529
530	env = xmalloc(sizeof(char *) * event->env_vars_c);
531	for (i = 0; i < event->env_vars_c; i++) {
532		env[i] = alloc_env(event->env_vars[i].key, event->env_vars[i].value);
533		putenv(env[i]);
534	}
535
536	for (i = 0; i < rule->actions_c; i++) {
537		switch (rule->actions[i].type) {
538			case ACT_STOP_PROCESSING:
539				res = 1;
540				break;
541			case ACT_STOP_IF_FAILED:
542				if (last_rv != 0)
543					res = 1;
544				break;
545			case ACT_NEXT_EVENT:
546				res = -1;
547				break;
548			case ACT_NEXT_IF_FAILED:
549				if (last_rv != 0)
550					res = -1;
551				break;
552			case ACT_MAKE_DEVICE:
553				last_rv = make_dev_from_event(event, rule->actions[i].parameter[0], strtoul(rule->actions[i].parameter[1], NULL, 0));
554				break;
555			case ACT_CHMOD:
556				last_rv = chmod_file(event, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
557				break;
558			case ACT_CHOWN:
559			case ACT_CHGRP:
560				last_rv = chown_chgrp(event, rule->actions[i].type, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
561				break;
562			case ACT_SYMLINK:
563				last_rv = make_symlink(event, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
564				break;
565			case ACT_RUN_SHELL:
566				last_rv = exec_shell(event, rule->actions[i].parameter[0]);
567				break;
568			case ACT_RUN_NOSHELL:
569				last_rv = exec_noshell(event, rule->actions[i].parameter[0], rule->actions[i].parameter);
570				break;
571			case ACT_SETENV:
572				last_rv = setenv(rule->actions[i].parameter[0], rule->actions[i].parameter[1], 1);
573				break;
574			case ACT_REMOVE:
575				last_rv = unlink(rule->actions[i].parameter[0]);
576				rmdir_p(rule->actions[i].parameter[0]);
577				break;
578			case ACT_DEBUG:
579				print_debug(event);
580				last_rv = 0;
581				break;
582		}
583		if (res != 0)
584			break;
585	}
586
587	for (i = 0; i < event->env_vars_c; i++) {
588		unsetenv(event->env_vars[i].key);
589		free(env[i]);
590	}
591	free(env);
592
593	return res;
594}
595
596/**
597 * Sets the flags of the given rule.
598 *
599 * @1 Rule structure
600 *
601 * Returns: void
602 */
603void rule_flags(struct rule_t *rule) {
604	int i;
605
606	for (i = 0; i < rule->actions_c; i++) {
607		switch (rule->actions[i].type) {
608			case ACT_FLAG_NOTHROTTLE:
609				rule->flags |= FLAG_NOTHROTTLE;
610				break;
611		}
612	}
613
614	return;
615}
616
617/**
618 * Checks whether the given character should initiate
619 * further parsing.
620 *
621 * @1 Character to examine
622 *
623 * Returns: 1 if it should, 0 otherwise
624 */
625static inline int isinitiator(int c) {
626	switch (c) {
627		case ',':
628		case ';':
629		case '{':
630		case '}':
631			return 1;
632	}
633
634	return 0;
635}
636
637/**
638 * Appends a character to a buffer. Enlarges if necessary.
639 *
640 * @1 Pointer to the buffer
641 * @2 Pointer to buffer size
642 * @3 Pointer to last buffer character
643 * @4 Appended character
644 *
645 * Returns: void
646 */
647static inline void add_buffer(char **buf, int *blen, int *slen, char c) {
648	if (*slen + 1 >= *blen) {
649		*blen = *blen + 64;
650		*buf = xrealloc(*buf, *blen);
651	}
652
653	(*buf)[*slen] = c;
654	(*buf)[*slen+1] = '\0';
655	*slen += 1;
656}
657
658/**
659 * Parses a string into a syntactically acceptable value.
660 *
661 * @1 Input string
662 * @2 Pointer to the new position
663 *
664 * Returns: Newly allocated string.
665 */
666static char *rules_get_value(char *input, char **nptr) {
667	int quotes = QUOTES_NONE;
668	char *ptr = input;
669
670	int blen, slen;
671	char *buf;
672
673	blen = slen = 0;
674	buf = NULL;
675
676	if (isinitiator(*ptr)) {
677		add_buffer(&buf, &blen, &slen, *ptr);
678		ptr++;
679		goto return_value;
680	}
681
682	while (isspace(*ptr) && *ptr != '\0')
683		ptr++;
684
685	if (*ptr == '\0')
686		return NULL;
687
688	switch (*ptr) {
689		case '"':
690			quotes = QUOTES_DOUBLE;
691			ptr++;
692			break;
693		case '\'':
694			quotes = QUOTES_SINGLE;
695			ptr++;
696			break;
697	}
698
699	if (quotes != QUOTES_NONE) {
700		while (quotes != QUOTES_NONE) {
701			switch (*ptr) {
702				case '\\':
703					ptr++;
704					add_buffer(&buf, &blen, &slen, *ptr);
705					break;
706				case '"':
707					if (quotes == QUOTES_DOUBLE)
708						quotes = QUOTES_NONE;
709					break;
710				case '\'':
711					if (quotes == QUOTES_SINGLE)
712						quotes = QUOTES_NONE;
713					break;
714				default:
715					add_buffer(&buf, &blen, &slen, *ptr);
716					break;
717			}
718			ptr++;
719		}
720	} else {
721		while (!isspace(*ptr) && *ptr != '\0') {
722			if (isinitiator(*ptr))
723				break;
724
725			if (*ptr == '\\')
726				ptr++;
727
728			add_buffer(&buf, &blen, &slen, *ptr);
729			ptr++;
730		}
731	}
732
733return_value:
734	while (isspace(*ptr) && *ptr != '\0')
735		ptr++;
736
737	if (nptr != NULL)
738		*nptr = ptr;
739
740	return buf;
741}
742
743/**
744 * Releases all memory associated with the ruleset. TODO: Make
745 * the behavior same for all _free() functions, ie. either
746 * release the given pointer itself or keep it, but do it
747 * in all functions!
748 *
749 * @1 The ruleset to be freed
750 *
751 * Returns: void
752 */
753void rules_free(struct rules_t *rules) {
754	int i, j, k;
755
756	for (i = 0; i < rules->rules_c; i++) {
757		for (j = 0; j < rules->rules[i].actions_c; j++) {
758			if (rules->rules[i].actions[j].parameter != NULL) {
759				for (k = 0; rules->rules[i].actions[j].parameter[k] != NULL; k++)
760					free(rules->rules[i].actions[j].parameter[k]);
761				free(rules->rules[i].actions[j].parameter);
762			}
763		}
764		for (j = 0; j < rules->rules[i].conditions_c; j++) {
765			free(rules->rules[i].conditions[j].key);
766			free(rules->rules[i].conditions[j].value);
767		}
768		free(rules->rules[i].actions);
769		free(rules->rules[i].conditions);
770	}
771	free(rules->rules);
772}
773
774/**
775 * Includes a rule file.
776 *
777 * @1 Filename
778 * @2 The ruleset structure
779 *
780 * Returns: 0 if success, -1 otherwise
781 */
782int rules_include(const char *filename, struct rules_t **return_rules) {
783	struct filemap_t filemap;
784	struct rules_t *rules;
785
786	if (map_file(filename, &filemap)) {
787		ERROR("rules parse","Unable to open/mmap rules file.");
788		return -1;
789	}
790
791	rules = rules_from_config((char*)(filemap.map), *return_rules);
792	if (rules == NULL) {
793		ERROR("rules parse","Unable to parse rules file.");
794		return -1;
795	}
796	*return_rules = rules;
797
798	unmap_file(&filemap);
799
800	return 0;
801}
802
803/**
804 * Parses an entire file of rules.
805 *
806 * @1 The whole file in memory or mmap'd
807 *
808 * Returns: A newly allocated ruleset.
809 */
810struct rules_t *rules_from_config(char *input, struct rules_t *return_rules) {
811	#define last_rule return_rules->rules[return_rules->rules_c - 1]
812	int nested;
813	int status;
814	int terminate;
815	char *buf;
816
817	/*
818	 * TODO: cleanup
819	 *
820	 * BIIIG cleanup... Use callbacks for actions and for internal actions.
821	 */
822
823	int i, j;
824	struct key_rec_t conditions[] = {	/*NOTE: We never have parameters for conditions. */
825		{"is", 0, COND_MATCH_IS},
826		{"==", 0, COND_MATCH_CMP},
827		{"!=", 0, COND_NMATCH_CMP},
828		{"~~", 0, COND_MATCH_RE},
829		{"!~", 0, COND_NMATCH_RE},
830		{NULL, 0, -1}
831	};
832
833	struct key_rec_t actions[] = {
834		/*one line / one command*/
835		{"run", 1, ACT_RUN_SHELL},
836		{"exec", -1, ACT_RUN_NOSHELL},
837		{"break", 0, ACT_STOP_PROCESSING},
838		{"break_if_failed", 0, ACT_STOP_IF_FAILED},
839		{"next", 0, ACT_NEXT_EVENT},
840		{"next_if_failed", 0, ACT_NEXT_IF_FAILED},
841		{"chown", 2, ACT_CHOWN},
842		{"chmod", 2, ACT_CHMOD},
843		{"chgrp", 2, ACT_CHGRP},
844		{"setenv", 2, ACT_SETENV},
845		{"remove", 1, ACT_REMOVE},
846		{"nothrottle", 0, ACT_FLAG_NOTHROTTLE},
847		{"printdebug", 0, ACT_DEBUG},
848		/*symlink*/
849		{"symlink", 2, ACT_SYMLINK},
850		{"softlink", 2, ACT_SYMLINK},
851		/*makedev*/
852		{"mknod", 2, ACT_MAKE_DEVICE},
853		{"makedev", 2, ACT_MAKE_DEVICE},
854		{NULL, 0, -1}
855	};
856
857	/*
858	 * A little trick for inclusion.
859	 */
860	if (return_rules == NULL) {
861		return_rules = xmalloc(sizeof(struct rules_t));
862		return_rules->rules_c = 1;
863		return_rules->rules = xmalloc(sizeof(struct rule_t) * return_rules->rules_c);
864		nested = 0;
865	} else {
866		nested = 1;
867	}
868
869	status = STATUS_KEY;
870
871	last_rule.actions = NULL;
872	last_rule.actions_c = 0;
873	last_rule.conditions = NULL;
874	last_rule.conditions_c = 0;
875
876	terminate = 0;
877	do {
878		buf = rules_get_value(input, &input);
879		if (buf == NULL) {
880			ERROR("rules_get_value", "Malformed rule - unable to read!");
881			terminate = 1;
882			break;
883		}
884
885		if (buf[0] == '#') {
886			/* Skip to next line */
887			while (*input != '\0' && *input != '\n')
888				input++;
889
890			free(buf);
891			continue;
892		} else if (buf[0] == '$') {
893			buf++;
894
895			/*
896			 * Warning, hack ahead...
897			 */
898			if (!strcmp("include", buf)) {
899				buf = rules_get_value(input, &input);
900				if (rules_include(buf, &return_rules)) {
901					ERROR("rules_include", "Unable to include ruleset '%s'!", buf);
902				}
903			}
904
905			free(buf);
906			continue;
907		}
908
909		switch (status) {
910			case STATUS_KEY:
911				last_rule.conditions_c++;
912				last_rule.conditions = xrealloc(last_rule.conditions, sizeof(struct condition_t) * last_rule.conditions_c);
913				last_rule.conditions[last_rule.conditions_c-1].key = strdup(buf);
914
915				status = STATUS_CONDTYPE;
916				break;
917			case STATUS_CONDTYPE:
918				last_rule.conditions[last_rule.conditions_c-1].type = -1;
919
920				for (i = 0; conditions[i].key != NULL; i++) {
921					if (!strcmp(conditions[i].key, buf)) {
922						last_rule.conditions[last_rule.conditions_c-1].type = conditions[i].type;
923						break;
924					}
925				}
926
927				if (last_rule.conditions[last_rule.conditions_c-1].type == -1) {
928					ERROR("rules_get_value / status / condtype", "Malformed rule - unknown condition type.");
929					terminate = 1;
930				}
931
932				status = STATUS_VALUE;
933				break;
934			case STATUS_VALUE:
935				last_rule.conditions[last_rule.conditions_c-1].value = strdup(buf);
936
937				status = STATUS_INITIATOR;
938				break;
939			case STATUS_INITIATOR:
940				if (!strcmp(buf, ",") || !strcmp(buf, ";")) {
941					status = STATUS_KEY;
942				} else if (!strcmp(buf, "{")) {
943					status = STATUS_ACTION;
944				} else {
945					ERROR("rules_get_value / status / initiator", "Malformed rule - unknown initiator.");
946					terminate = 1;
947				}
948				break;
949			case STATUS_ACTION:
950				if (!strcmp(buf, "}")) {
951					status = STATUS_KEY;
952					return_rules->rules_c++;
953					return_rules->rules = xrealloc(return_rules->rules, sizeof(struct rule_t) * return_rules->rules_c);
954
955					last_rule.actions = NULL;
956					last_rule.actions_c = 0;
957					last_rule.conditions = NULL;
958					last_rule.conditions_c = 0;
959					break;
960				}
961
962				last_rule.actions_c++;
963				last_rule.actions = xrealloc(last_rule.actions, sizeof(struct action_t) * last_rule.actions_c);
964				last_rule.actions[last_rule.actions_c-1].parameter = NULL;
965				last_rule.actions[last_rule.actions_c-1].type = -1;
966
967				for (i = 0; actions[i].key != NULL; i++) {
968					if (!strcmp(actions[i].key, buf)) {
969						last_rule.actions[last_rule.actions_c-1].type = actions[i].type;
970						break;
971					}
972				}
973
974				if (last_rule.actions[last_rule.actions_c-1].type == -1) {
975					ERROR("rules_get_value / status / action", "Malformed rule - unknown action: %s.", buf);
976					terminate = 1;
977				}
978
979				if (actions[i].param > 0) {
980					last_rule.actions[last_rule.actions_c-1].parameter = xmalloc(sizeof(char*) * (actions[i].param + 1));
981					last_rule.actions[last_rule.actions_c-1].parameter[actions[i].param] = NULL;
982
983					for (j = 0; j < actions[i].param; j++) {
984						last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
985						if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], "}")) {
986							ERROR("rules_get_value / status / action", "Malformed rule - not enough parameters passed.");
987							status = STATUS_KEY;
988							break;
989						}
990						last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\}", "}");
991					}
992				} else if (actions[i].param == -1) {
993					j = 0;
994					last_rule.actions[last_rule.actions_c-1].parameter = xmalloc(sizeof(char*) * (j + 1));
995					last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
996					while (last_rule.actions[last_rule.actions_c-1].parameter[j] != NULL) {
997						if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], ";")) {
998							break;
999						}
1000						if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], "}")) {
1001							ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
1002							status = STATUS_KEY;
1003							break;
1004						}
1005						if (last_rule.actions[last_rule.actions_c-1].parameter[j][0] == '\0') {
1006							ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
1007							status = STATUS_KEY;
1008							break;
1009						}
1010						last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\}", "}");
1011						last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\;", ";");
1012
1013						j++;
1014						last_rule.actions[last_rule.actions_c-1].parameter = xrealloc(last_rule.actions[last_rule.actions_c-1].parameter, sizeof(char*) * (j + 1));
1015						last_rule.actions[last_rule.actions_c-1].parameter[j]  = rules_get_value(input, &input);
1016					}
1017					free(last_rule.actions[last_rule.actions_c-1].parameter[j]);
1018					last_rule.actions[last_rule.actions_c-1].parameter[j] = NULL;
1019				}
1020
1021				if (status == STATUS_KEY) {
1022					return_rules->rules_c++;
1023					return_rules->rules = xrealloc(return_rules->rules, sizeof(struct rule_t) * return_rules->rules_c);
1024
1025					last_rule.actions = NULL;
1026					last_rule.actions_c = 0;
1027					last_rule.conditions = NULL;
1028					last_rule.conditions_c = 0;
1029				}
1030				break;
1031		}
1032
1033		free(buf);
1034	} while (*input != '\0' && !terminate);
1035
1036	if (!terminate) {
1037		/* A little bit hacky cleanup */
1038		if (!nested)
1039			return_rules->rules_c--;
1040		return return_rules;
1041	} else {
1042		/*
1043		 * We don't want to cleanup if we're nested.
1044		 */
1045		if (!nested) {
1046			rules_free(return_rules);
1047			free(return_rules);
1048		}
1049
1050		return NULL;
1051	}
1052}
1053