Blob Blame History Raw
From e80c6641546f23a305ff84f8dac9ca6f8105beb3 Mon Sep 17 00:00:00 2001
From: Radovan Sroka <rsroka@redhat.com>
Date: Fri, 24 Jun 2022 15:03:28 +0200
Subject: [PATCH] Introduce filtering of rpmdb

- this feature introduces very flexible filter syntax
- original filter was compiled in so this is very useful
- filter needs to keep a minimal set of files that will be excuted
  on the system eventually
- all the configuration can be done in /etc/fapolicyd/rpm-filter.conf

Signed-off-by: Radovan Sroka <rsroka@redhat.com>
---
 doc/Makefile.am           |   3 +-
 doc/rpm-filter.conf.5     |  61 ++++++
 fapolicyd.spec            |   1 +
 init/Makefile.am          |   2 +
 init/rpm-filter.conf      |  42 ++++
 src/Makefile.am           |   8 +-
 src/library/llist.c       |  23 ++-
 src/library/llist.h       |   1 +
 src/library/rpm-backend.c |  76 ++-----
 src/library/rpm-filter.c  | 419 ++++++++++++++++++++++++++++++++++++++
 src/library/rpm-filter.h  |  67 ++++++
 src/library/stack.c       |  82 ++++++++
 src/library/stack.h       |  41 ++++
 13 files changed, 757 insertions(+), 69 deletions(-)
 create mode 100644 doc/rpm-filter.conf.5
 create mode 100644 init/rpm-filter.conf
 create mode 100644 src/library/rpm-filter.c
 create mode 100644 src/library/rpm-filter.h
 create mode 100644 src/library/stack.c
 create mode 100644 src/library/stack.h

diff --git a/doc/Makefile.am b/doc/Makefile.am
index f0b79080..726218ed 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -28,4 +28,5 @@ man_MANS = \
 	fapolicyd-cli.1 \
 	fapolicyd.rules.5 \
 	fapolicyd.trust.5 \
-	fapolicyd.conf.5
+	fapolicyd.conf.5 \
+	rpm-filter.conf.5
diff --git a/doc/rpm-filter.conf.5 b/doc/rpm-filter.conf.5
new file mode 100644
index 00000000..5a7e1e3e
--- /dev/null
+++ b/doc/rpm-filter.conf.5
@@ -0,0 +1,61 @@
+.TH RPM_FILTER.CONF: "5" "January 2023" "Red Hat" "System Administration Utilities"
+.SH NAME
+rpm-filter.conf \- fapolicyd filter configuration file
+.SH DESCRIPTION
+The file
+.I /etc/fapolicyd/rpm-filter.conf
+contains configuration of the filter for the application allowlisting daemon. This filter specifies an allow or exclude list of files from rpm. Valid line starts with character '+', '-' or '#' for comments. The rest of the line contains a path specification. Space can be used as indentation to add more specific filters to the previous one. If  there are multiple specifications on the same indentation level they extend the previous line with lower indentation, usually a directory.  The path may be specified using the glob pattern.
+
+The filters are processed as follows: Starting from the up the to bottom while in case of a match the result (+/-) is set unless there is an indented block which describes more detailed specification of the parent level match. The same processing logic is applied to the inner filters definitions. If there is no match, the parent’s result is set.
+
+If the result was a plus (+), the respective file from the rpmdb is imported to the TrustDB. Vice versa, if the result was a minus (-), the respective file is not imported.
+
+From a performance point of view it is better to design an indented filter because in the ideal situation each component of the path is compared only once. In contrast to it, a filter without any indentation has to contain a full path which makes the pattern more complicated and thus slower to process. The motivation behind this is to have a flexible configuration and keep the TrustDB as small as possible to make the look-ups faster.
+
+
+.nf
+.B # this is simle black list
+.B - /usr/bin/some_binary1
+.B - /usr/bin/some_binary2
+.B + /
+.fi
+
+.nf
+.B # this is the same 
+.B + /
+.B \ + usr/bin/
+.B \ \ - some_binary1
+.B \ \ - some_binary2
+.fi
+
+.nf
+.B # this is similar black list with a wildcard
+.B - /usr/bin/some_binary?
+.B + /
+.fi
+
+.nf
+.B # this is similar with another wildcard
+.B + /
+.B \ - usr/bin/some_binary*
+.fi
+
+.nf
+.B # keeps everything except usr/share except python and perl files
+.B # /usr/bin/ls - result is '+'
+.B # /usr/share/something - result is '-'
+.B # /usr/share/abcd.py - result is '+'
+.B + /
+.B \ - usr/share/
+.B \ \ + *.py
+.B \ \ + *.pl
+.fi
+
+.SH "SEE ALSO"
+.BR fapolicyd (8),
+.BR fapolicyd-cli (1)
+and
+.BR fapolicy.rules (5).
+
+.SH AUTHOR
+Radovan Sroka
diff --git a/init/Makefile.am b/init/Makefile.am
index da948e4e..1f23dffe 100644
--- a/init/Makefile.am
+++ b/init/Makefile.am
@@ -1,6 +1,7 @@
 EXTRA_DIST = \
 	fapolicyd.service \
 	fapolicyd.conf \
+	rpm-filter.conf \
 	fapolicyd.trust \
 	fapolicyd-tmpfiles.conf \
 	fapolicyd-magic \
@@ -11,6 +12,7 @@ fapolicyddir = $(sysconfdir)/fapolicyd
 
 dist_fapolicyd_DATA = \
 	fapolicyd.conf \
+	rpm-filter.conf \
 	fapolicyd.trust
 
 systemdservicedir = $(systemdsystemunitdir)
diff --git a/init/rpm-filter.conf b/init/rpm-filter.conf
new file mode 100644
index 00000000..0c8fca40
--- /dev/null
+++ b/init/rpm-filter.conf
@@ -0,0 +1,42 @@
+# default filter file for fedora
+
++ /
+ - usr/include/
+ - usr/share/
+  # Python byte code
+  + *.py?
+  # Python text files
+  + *.py
+  # Some apps have a private libexec
+  + */libexec/*
+  # Ruby
+  + *.rb
+  # Perl
+  + *.pl
+  # System tap
+  + *.stp
+  # Javascript
+  + *.js
+  # Java archive
+  + *.jar
+  # M4
+  + *.m4
+  # PHP
+  + *.php
+  # Perl Modules
+  + *.pm
+  # Lua
+  + *.lua
+  # Java
+  + *.class
+  # Typescript
+  + *.ts
+  # Typescript JSX
+  + *.tsx
+  # Lisp
+  + *.el
+  # Compiled Lisp
+  + *.elc
+ - usr/src/kernel*/
+  + */scripts/*
+  + */tools/objtool/*
diff --git a/src/Makefile.am b/src/Makefile.am
index 547ea486..fd08eb06 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,13 +62,19 @@ libfapolicyd_la_SOURCES = \
 	library/subject-attr.h \
 	library/subject.c \
 	library/subject.h \
+	library/stack.c \
+	library/stack.h \
 	library/string-util.c \
 	library/string-util.h \
 	library/trust-file.c \
 	library/trust-file.h
 
 if WITH_RPM
-libfapolicyd_la_SOURCES += library/rpm-backend.c
+libfapolicyd_la_SOURCES += \
+	library/rpm-backend.c \
+	library/rpm-filter.c \
+	library/rpm-filter.h
+
 endif
 
 libfapolicyd_la_CFLAGS = $(fapolicyd_CFLAGS)
diff --git a/src/library/llist.c b/src/library/llist.c
index 6132805a..44cfb4a3 100644
--- a/src/library/llist.c
+++ b/src/library/llist.c
@@ -45,19 +45,36 @@ list_item_t *list_get_first(const list_t *list)
 	return list->first;
 }
 
-
-int list_append(list_t *list, const char *index, const char *data)
+static list_item_t * create_item(const char *index, const char *data)
 {
 	list_item_t *item = malloc(sizeof(list_item_t));
 	if (!item) {
 		msg(LOG_ERR, "Malloc failed");
-		return 1;
+		return item;
 	}
 
 	item->index = index;
 	item->data = data;
 	item->next = NULL;
 
+	return item;
+}
+
+int list_prepend(list_t *list, const char *index, const char *data)
+{
+	list_item_t *item = create_item(index, data);
+
+	item->next = list->first;
+	list->first = item;
+
+	++list->count;
+	return 0;
+}
+
+int list_append(list_t *list, const char *index, const char *data)
+{
+	list_item_t *item = create_item(index, data);
+
 	if (list->first) {
 		list->last->next = item;
 		list->last = item;
diff --git a/src/library/llist.h b/src/library/llist.h
index 0c1d85a7..59eccf17 100644
--- a/src/library/llist.h
+++ b/src/library/llist.h
@@ -40,6 +40,7 @@ typedef struct list_header {
 
 void list_init(list_t *list);
 list_item_t *list_get_first(const list_t *list);
+int list_prepend(list_t *list, const char *index, const char *data);
 int list_append(list_t *list, const char *index, const char *data);
 void list_destroy_item(list_item_t **item);
 void list_empty(list_t *list);
diff --git a/src/library/rpm-backend.c b/src/library/rpm-backend.c
index 7f1af438..cc6427ab 100644
--- a/src/library/rpm-backend.c
+++ b/src/library/rpm-backend.c
@@ -40,6 +40,8 @@
 #include "fapolicyd-backend.h"
 #include "llist.h"
 
+#include "rpm-filter.h"
+
 static int rpm_init_backend(void);
 static int rpm_load_list(const conf_t *);
 static int rpm_destroy_backend(void);
@@ -176,69 +178,6 @@ static void close_rpm(void)
 	rpmlogClose();
 }
 
-// This function will check a passed file name to see if the path should
-// be kept or dropped. 1 means discard it, and 0 means keep it.
-static int drop_path(const char *file_name)
-{
-	const char *p = file_name;
-	if (!strncmp(p, "/usr", 4)) {
-		p += 4;
-
-		// Drop anything in /usr/include
-		if (!strncmp(p, "/include", 8))
-			return 1;
-
-		// Only keep languages from /usr/share
-		if (!strncmp(p, "/share", 6)) {
-			p += 6;
-			
-			// These are roughly ordered by quantity
-			static const char *arr_share[] = {
-				"*.py?",       // Python byte code
-				"*.py",        // Python text files
-				"*/libexec/*", // Some apps have a private libexec
-				"*.rb",        // Ruby
-				"*.pl",        // Perl
-				"*.stp",       // System tap
-				"*.js",        // Javascript
-				"*.jar",       // Java archive
-				"*.m4",        // M4
-				"*.php",       // PHP
-				"*.pm",        // Perl Modules
-				"*.lua",       // Lua
-				"*.class",     // Java
-				"*.ts",        // Typescript
-				"*.tsx",       // Typescript JSX
-				"*.el",        // Lisp
-				"*.elc",       // Compiled Lisp
-				NULL
-			};
-
-			for (int i = 0; arr_share[i]; ++i)
-				if (!fnmatch(arr_share[i], p, 0))
-					return 0;
-			return 1;
-		}
-
-		// Akmod needs scripts in /usr/src/kernel
-		if (!strncmp(p, "/src/kernel", 11)) {
-			p += 11;
-			
-			static const char *arr_src_kernel[] = {
-				"*/scripts/*",
-				"*/tools/objtool/*",
-				NULL
-			};
-			
-			for (int i = 0; arr_src_kernel[i]; ++i)
-				if (!fnmatch(arr_src_kernel[i], p, 0))
-					return 0;
-			return 1;
-		}
-	}
-	return 0;
-}
-
 struct _hash_record {
 	const char * key;
 	UT_hash_handle hh;
@@ -290,7 +229,8 @@ static int rpm_load_list(const conf_t *conf)
 			if (file_name == NULL)
 				continue;
 
-			if (drop_path(file_name)) {
+			// should we drop a path?
+			if (!filter_check(file_name)) {
 				free((void *)file_name);
 				free((void *)sha);
 				continue;
@@ -358,12 +298,20 @@ static int rpm_load_list(const conf_t *conf)
 
 static int rpm_init_backend(void)
 {
+	if (filter_init())
+		return 1;
+
+	if (filter_load_file())
+		return 1;
+
 	list_init(&rpm_backend.list);
+
 	return 0;
 }
 
 static int rpm_destroy_backend(void)
 {
+	filter_destroy();
 	list_empty(&rpm_backend.list);
 	return 0;
 }
diff --git a/src/library/rpm-filter.c b/src/library/rpm-filter.c
new file mode 100644
index 00000000..806989e9
--- /dev/null
+++ b/src/library/rpm-filter.c
@@ -0,0 +1,419 @@
+/*
+* rpm-filter.c - filter for rpm trust source
+* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
+* Boston, MA 02110-1335, USA.
+*
+* Authors:
+*   Radovan Sroka <rsroka@redhat.com>
+*/
+
+#include "rpm-filter.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <fnmatch.h>
+
+#include "llist.h"
+#include "stack.h"
+#include "message.h"
+#include "string-util.h"
+
+
+#define RPM_FILTER_FILE "/etc/fapolicyd/rpm-filter.conf"
+
+rpm_filter_t *global_filter = NULL;
+
+static rpm_filter_t *filter_create_obj(void);
+static void filter_destroy_obj(rpm_filter_t *_filter);
+
+// init fuction of this module
+int filter_init(void)
+{
+	global_filter = filter_create_obj();
+	if (global_filter == NULL)
+		return 1;
+
+	return 0;
+}
+
+// destroy funtion of this module
+void filter_destroy(void)
+{
+	filter_destroy_obj(global_filter);
+}
+
+// alocate new filter object and fill with the defaults
+static rpm_filter_t *filter_create_obj(void)
+{
+	rpm_filter_t *filter = malloc(sizeof(rpm_filter_t));
+	if (filter) {
+		filter->type = NONE;
+		filter->path = NULL;
+		filter->len = 0;
+		filter->matched = 0;
+		filter->processed = 0;
+		list_init(&filter->list);
+	}
+	return filter;
+}
+
+// recusively free all the nested filters
+// and also free itself
+static void filter_destroy_obj(rpm_filter_t *_filter)
+{
+	if (_filter == NULL)
+		return;
+
+	list_item_t *item = list_get_first(&_filter->list);
+	for (; item != NULL ; item = item->next) {
+		filter_destroy_obj(((stack_item_t *)item->data)->filter);
+	}
+
+	(void)free(_filter->path);
+	list_empty(&_filter->list);
+}
+
+// create struct and push it to the top of stack
+static void stack_push_vars(stack_t *_stack, int _level, int _offset, rpm_filter_t *_filter)
+{
+	if (_stack == NULL)
+		return;
+
+	stack_item_t *item = malloc(sizeof(stack_item_t));
+	if (item == NULL)
+		return;
+
+	item->level = _level;
+	item->offset = _offset;
+	item->filter = _filter;
+
+	stack_push(_stack, item);
+}
+
+// helper function to reset to some default values when the top is going to be popped
+static void stack_set_pop(stack_t *_stack)
+{
+	stack_item_t *stack_item = (stack_item_t*)stack_top(_stack);
+	if (stack_item) {
+		stack_item->filter->matched = 0;
+		stack_item->filter->processed = 0;
+	}
+	stack_pop(_stack);
+}
+
+// this funtion gets full path and checks it against filter
+// returns 1 for keeping the file and 0 for dropping it
+int filter_check(const char *_path)
+{
+	rpm_filter_t *filter = global_filter;
+
+	char *path = strdup(_path);
+	size_t path_len = strlen(_path);
+	size_t offset = 0;
+	// Create a stack to store the filters that need to be checked
+	stack_t stack;
+	stack_init(&stack);
+
+	int res = 0;
+	int level = 0;
+
+	stack_push_vars(&stack, level, offset, filter);
+
+	// start of the non recursive check
+	while(!stack_is_empty(&stack)) {
+
+		int matched = 0;
+		filter->processed = 1;
+
+		// this is starting branch of the algo
+		// assuming that in root filter filter_> path is NULL
+		if (filter->path == NULL) {
+			list_item_t *item = list_get_first(&filter->list);
+
+			// push all the descendants to the stack
+			for (; item != NULL ; item = item->next) {
+				rpm_filter_t *next_filter = ((stack_item_t*)item->data)->filter;
+				stack_push_vars(&stack, level+1, offset, next_filter);
+			}
+		// usual branch, start with processing
+		} else {
+			// wildcard contition
+			char *is_wildcard = strpbrk(filter->path, "?*[");
+			if (is_wildcard) {
+				int count = 0;
+				char *filter_lim, *filter_old_lim;
+				filter_lim = filter_old_lim = filter->path;
+
+				char *path_lim, *path_old_lim;
+				path_lim = path_old_lim = path+offset;
+
+				// there can be wildcard in the dir name so we will
+				// how many chars will be eaten
+				// the intention is count the offset down below
+				while(1) {
+					filter_lim = strchr(filter_lim, '/');
+					path_lim = strchr(path_lim, '/');
+
+					if (filter_lim) {
+						count++;
+						filter_old_lim = filter_lim;
+						filter_lim++;
+					} else
+						break;
+
+					if (path_lim) {
+						path_old_lim = path_lim;
+						path_lim++;
+					} else
+						break;
+
+				}
+				// put 0 after the last /
+				char tmp = '\0';
+				if (count && *(filter_old_lim+1) == '\0') {
+					 tmp = *(path_old_lim+1);
+					*(path_old_lim+1) = '\0';
+				}
+
+				// check fnmatch
+				matched = !fnmatch(filter->path, path+offset, 0);
+
+				// and set back
+				if (count && *(filter_old_lim+1) == '\0')
+					*(path_old_lim+1) = tmp;
+
+				if (matched) {
+					offset = path_old_lim - path+offset;
+				}
+			} else {
+				// match normal path or just specific part of it 
+				matched = !strncmp(path+offset, filter->path, filter->len);
+
+				if (matched)
+					offset += filter->len;
+			}
+
+
+			if (matched) {
+				level++;
+				filter->matched = 1;
+
+				// if matched we need ot push descendants to the stack
+				list_item_t *item = list_get_first(&filter->list);
+
+				// if there are no descendants and it is a wildcard then it's a match
+				if (item == NULL && is_wildcard) {
+					// if '+' ret 1 and if '-' ret 0
+					res = filter->type == ADD ? 1 : 0;
+					goto end;
+				}
+
+				// no descendants, and already compared whole path string so its a match
+				if (item == NULL && path_len == offset) {
+					// if '+' ret 1 and if '-' ret 0
+					res = filter->type == ADD ? 1 : 0;
+					goto end;
+				}
+
+				// push descendants to the stack
+				for (; item != NULL ; item = item->next) {
+					rpm_filter_t *next_filter = ((stack_item_t*)item->data)->filter;
+					stack_push_vars(&stack, level, offset, next_filter);
+				}
+
+			}
+
+		}
+
+		stack_item_t * stack_item = NULL;
+
+		// popping processed filters from the top of the stack
+		do {
+			if (stack_item) {
+				filter = stack_item->filter;
+				offset = stack_item->offset;
+				level = stack_item->level;
+
+				// assuimg that nothing has matched on the upper level so it's a directory match
+				if (filter->matched && filter->path[filter->len-1] == '/') {
+					res = filter->type == ADD ? 1 : 0;
+					goto end;
+				}
+
+				stack_set_pop(&stack);
+			}
+
+			stack_item = (stack_item_t*)stack_top(&stack);
+		} while(stack_item && stack_item->filter->processed);
+
+		if (!stack_item)
+			break;
+
+		filter = stack_item->filter;
+		offset = stack_item->offset;
+		level = stack_item->level;
+	}
+
+end:
+	// Clean up the stack
+	while(!stack_is_empty(&stack))
+		{stack_set_pop(&stack);}
+	stack_destroy(&stack);
+	free(path);
+	return res;
+}
+
+// load rpm filter configuration file and fill the filter structure
+int filter_load_file(void)
+{
+
+	FILE *stream = fopen(RPM_FILTER_FILE, "r");
+
+	if (stream == NULL) {
+		msg(LOG_ERR, "Cannot open filter file %s", RPM_FILTER_FILE);
+		return 1;
+	}
+
+	ssize_t nread;
+	size_t len = 0;
+	char * line = NULL;
+	long line_number = 0;
+	int last_level = 0;
+	rpm_filter_t * last_filter = global_filter;
+
+	stack_t stack;
+	stack_init(&stack);
+	stack_push_vars(&stack, last_level, 0, last_filter);
+
+	while ((nread = getline(&line, &len, stream)) != -1) {
+		line_number++;
+
+		if (line[0] == '\0' || line[0] == '\n') {
+			free(line);
+			line = NULL;
+			continue;
+		}
+
+		// get rid of the new line char
+		char * new_line = strchr(line, '\n');
+		if (new_line) {
+			*new_line = '\0';
+			len--;
+		}
+
+		int level = 1;
+		char * rest = line;
+		rpm_filter_type_t type = NONE;
+
+		for (size_t i = 0 ; i < len ; i++) {
+			switch (line[i]) {
+				case ' ':
+					level++;
+					continue;
+				case '+':
+					type = ADD;
+					break;
+				case '-':
+					type = SUB;
+					break;
+				case '#':
+					type = COMMENT;
+					break;
+				default:
+					type = BAD;
+					break;
+			}
+
+			// continue with next char
+			// skip + and space
+			rest = fapolicyd_strtrim(&(line[i+2]));
+			break;
+		}
+
+		// ignore comment
+		if (type == COMMENT) {
+			free(line);
+			line = NULL;
+			continue;
+		}
+
+		// if something bad return error
+		if (type == BAD) {
+			msg(LOG_ERR, "filter_load_file: cannot parse line number %ld, \"%s\"", line_number, line);
+			free(line);
+			line = NULL;
+			fclose(stream);
+			stack_destroy(&stack);
+			filter_destroy();
+			return 1;
+		}
+
+		rpm_filter_t * filter = filter_create_obj();
+
+		if (filter) {
+			filter->path = strdup(rest);
+			filter->len = strlen(filter->path);
+			filter->type = type;
+		}
+
+		// comparing level of indetantion between the last line and the current one 
+		last_level = ((stack_item_t*)stack_top(&stack))->level;
+		if (level == last_level) {
+			stack_pop(&stack);
+
+			stack_push_vars(&((stack_item_t*)stack_top(&stack))->filter->list, level, 0, filter);
+			stack_push_vars(&stack, level, 0, filter);
+
+		} else if (level == last_level + 1) {
+
+			stack_push_vars(&((stack_item_t*)stack_top(&stack))->filter->list, level, 0, filter);
+			stack_push_vars(&stack, level, 0, filter);
+
+		} else if (level < last_level){
+			for (int i = 0 ; i < last_level - level + 1; i++) {
+				stack_pop(&stack);
+			}
+
+			stack_push_vars(&((stack_item_t*)stack_top(&stack))->filter->list, level, 0, filter);
+			stack_push_vars(&stack, level, 0, filter);
+		} else {
+			msg(LOG_ERR, "filter_load_file: paring error line: %ld, \"%s\"", line_number, line);
+			free(line);
+			line = NULL;
+			fclose(stream);
+			stack_destroy(&stack);
+			filter_destroy();
+			return 1;
+		}
+
+		last_filter = filter;
+
+		free(line);
+		line = NULL;
+	}
+
+	fclose(stream);
+	stack_destroy(&stack);
+	if (global_filter->list.count == 0) {
+		filter_destroy();
+		msg(LOG_ERR, "filter_load_file: no valid filter provided in %s", RPM_FILTER_FILE);
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/library/rpm-filter.h b/src/library/rpm-filter.h
new file mode 100644
index 00000000..2c49d338
--- /dev/null
+++ b/src/library/rpm-filter.h
@@ -0,0 +1,67 @@
+/*
+* rpm-filter.h - Header for rpm filter implementation
+* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
+* Boston, MA 02110-1335, USA.
+*
+* Authors:
+*   Radovan Sroka <rsroka@redhat.com>
+*/
+
+#ifndef FILTER_H_
+#define FILTER_H_
+
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "llist.h"
+
+typedef enum rpm_filter_type
+{
+	NONE,
+	ADD,
+	SUB,
+	COMMENT,
+	BAD,
+} rpm_filter_type_t;
+
+typedef struct _rpm_filter
+{
+	rpm_filter_type_t type;
+	char * path;
+	size_t len;
+	int processed;
+	int matched;
+	list_t list;
+} rpm_filter_t;
+
+
+typedef struct _stack_item
+{
+	int level;
+	int offset;
+	rpm_filter_t *filter;
+} stack_item_t;
+
+
+int filter_init(void);
+void filter_destroy(void);
+int filter_check(const char *_path);
+int filter_load_file(void);
+
+
+#endif // FILTER_H_
diff --git a/src/library/stack.c b/src/library/stack.c
new file mode 100644
index 00000000..2b04db86
--- /dev/null
+++ b/src/library/stack.c
@@ -0,0 +1,82 @@
+/*
+* stack.c - generic stack impementation
+* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
+* Boston, MA 02110-1335, USA.
+*
+* Authors:
+*   Radovan Sroka <rsroka@redhat.com>
+*/
+
+#include "stack.h"
+#include <stddef.h>
+
+// init of the stack struct
+void stack_init(stack_t *_stack)
+{
+	if (_stack == NULL)
+		return;
+	list_init(_stack);
+}
+
+// free all the resources from the stack
+void stack_destroy(stack_t *_stack)
+{
+
+	if (_stack == NULL)
+		return;
+	list_empty(_stack);
+}
+
+// push to the top of the stack
+void stack_push(stack_t *_stack, void *_data)
+{
+	list_prepend(_stack, NULL, (void *)_data);
+}
+
+// pop the the top without returning what was on the top
+void stack_pop(stack_t *_stack)
+{
+	if (_stack == NULL)
+		return;
+
+	list_item_t *first = _stack->first;
+	_stack->first = first->next;
+	list_destroy_item(&first);
+	_stack->count--;
+
+	return;
+}
+
+// function returns 1 if stack is emtpy 0 if it's not
+int stack_is_empty(stack_t *_stack)
+{
+	if (_stack == NULL)
+		return 1;
+
+	if (_stack->count == 0)
+		return 1;
+
+	return 0;
+}
+
+// return top of the stack without popping
+const void *stack_top(stack_t *_stack)
+{
+
+	return _stack->first ? _stack->first->data : NULL;
+}
diff --git a/src/library/stack.h b/src/library/stack.h
new file mode 100644
index 00000000..042476e3
--- /dev/null
+++ b/src/library/stack.h
@@ -0,0 +1,41 @@
+/*
+* stack.h - header for generic stack implementation
+* Copyright (c) 2023 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
+* Boston, MA 02110-1335, USA.
+*
+* Authors:
+*   Radovan Sroka <rsroka@redhat.com>
+*/
+
+
+#ifndef STACK_H_
+#define STACK_H_
+
+#include "llist.h"
+
+typedef list_t stack_t;
+
+void stack_init(stack_t *_stack);
+void stack_destroy(stack_t *_stack);
+void stack_push(stack_t *_stack, void *_data);
+void stack_pop(stack_t *_stack);
+int stack_is_empty(stack_t *_stack);
+const void *stack_top(stack_t *_stack);
+
+
+#endif // STACK_H_