aboutsummaryrefslogtreecommitdiffstats
path: root/src/fids/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fids/main.c')
-rw-r--r--src/fids/main.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/src/fids/main.c b/src/fids/main.c
new file mode 100644
index 000000000..c899b55e1
--- /dev/null
+++ b/src/fids/main.c
@@ -0,0 +1,371 @@
1/*
2 * Copyright (C) 2014-2021 Firejail Authors
3 *
4 * This file is part of firejail project
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/
20#include "fids.h"
21#include <sys/types.h>
22#include <sys/stat.h>
23#include <unistd.h>
24#include <fcntl.h>
25#include <sys/mman.h>
26#include <dirent.h>
27#include <glob.h>
28
29#define MAXBUF 4096
30
31static int dir_level = 1;
32static int include_level = 0;
33int arg_init = 0;
34int arg_check = 0;
35char *arg_homedir = NULL;
36char *arg_dbfile = NULL;
37
38int f_scanned = 0;
39int f_modified = 0;
40int f_new = 0;
41int f_removed = 0;
42int f_permissions = 0;
43
44
45
46static inline int is_dir(const char *fname) {
47 assert(fname);
48
49 struct stat s;
50 if (stat(fname, &s) == 0) {
51 if (S_ISDIR(s.st_mode))
52 return 1;
53 }
54 return 0;
55}
56
57static inline int is_link(const char *fname) {
58 assert(fname);
59
60 char c;
61 ssize_t rv = readlink(fname, &c, 1);
62 return (rv != -1);
63}
64
65// mode is an array of 10 chars or more
66static inline void file_mode(const char *fname, char *mode) {
67 assert(fname);
68 assert(mode);
69
70 struct stat s;
71 if (stat(fname, &s)) {
72 *mode = '\0';
73 return;
74 }
75
76 sprintf(mode, (s.st_mode & S_IRUSR) ? "r" : "-");
77 sprintf(mode + 1, (s.st_mode & S_IWUSR) ? "w" : "-");
78 sprintf(mode + 2, (s.st_mode & S_IXUSR) ? "x" : "-");
79 sprintf(mode + 3, (s.st_mode & S_IRGRP) ? "r" : "-");
80 sprintf(mode + 4, (s.st_mode & S_IWGRP) ? "w" : "-");
81 sprintf(mode + 5, (s.st_mode & S_IXGRP) ? "x" : "-");
82 sprintf(mode + 6, (s.st_mode & S_IROTH) ? "r" : "-");
83 sprintf(mode + 7, (s.st_mode & S_IWOTH) ? "w" : "-");
84 sprintf(mode + 8, (s.st_mode & S_IXOTH) ? "x" : "-");
85}
86
87
88static void file_checksum(const char *fname) {
89 assert(fname);
90
91 int fd = open(fname, O_RDONLY);
92 if (fd == -1)
93 return;
94
95 off_t size = lseek(fd, 0, SEEK_END);
96 if (size < 0) {
97 close(fd);
98 return;
99 }
100
101 char *content = "empty";
102 int mmapped = 0;
103 if (size == 0) {
104 // empty files don't mmap - use "empty" string as the file content
105 size = 6; // strlen("empty") + 1
106 }
107 else {
108 content = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
109 close(fd);
110 mmapped = 1;
111 }
112
113 unsigned char checksum[KEY_SIZE / 8];
114 blake2b(checksum, sizeof(checksum), content, size);
115 if (mmapped)
116 munmap(content, size);
117
118 // calculate blake2 checksum
119 char str_checksum[(KEY_SIZE / 8) * 2 + 1];
120 int long unsigned i;
121 char *ptr = str_checksum;
122 for (i = 0; i < sizeof(checksum); i++, ptr += 2)
123 sprintf(ptr, "%02x", (unsigned char ) checksum[i]);
124
125 // build permissions string
126 char mode[10];
127 file_mode(fname, mode);
128
129 if (arg_init)
130 printf("%s\t%s\t%s\n", mode, str_checksum, fname);
131 else if (arg_check)
132 db_check(fname, str_checksum, mode);
133 else
134 assert(0);
135
136 f_scanned++;
137 if (f_scanned % 500 == 0)
138 fprintf(stderr, "%d ", f_scanned);
139 fflush(0);
140}
141
142void list_directory(const char *fname) {
143 assert(fname);
144 if (dir_level > MAX_DIR_LEVEL) {
145 fprintf(stderr, "Warning fids: maximum depth level exceeded for %s\n", fname);
146 return;
147 }
148
149 if (db_exclude_check(fname))
150 return;
151
152 if (is_link(fname))
153 return;
154
155 if (!is_dir(fname)) {
156 file_checksum(fname);
157 return;
158 }
159
160 DIR *dir;
161 struct dirent *entry;
162
163 if (!(dir = opendir(fname)))
164 return;
165
166 dir_level++;
167 while ((entry = readdir(dir)) != NULL) {
168 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
169 continue;
170 char *path;
171 if (asprintf(&path, "%s/%s", fname, entry->d_name) == -1)
172 errExit("asprintf");
173 list_directory(path);
174 free(path);
175 }
176 closedir(dir);
177 dir_level--;
178}
179
180void globbing(const char *fname) {
181 assert(fname);
182
183 // filter top directory
184 if (strcmp(fname, "/") == 0)
185 return;
186
187 glob_t globbuf;
188 int globerr = glob(fname, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf);
189 if (globerr) {
190 fprintf(stderr, "Error fids: failed to glob pattern %s\n", fname);
191 exit(1);
192 }
193
194 long unsigned i;
195 for (i = 0; i < globbuf.gl_pathc; i++) {
196 char *path = globbuf.gl_pathv[i];
197 assert(path);
198
199 list_directory(path);
200 }
201
202 globfree(&globbuf);
203}
204
205static void process_config(const char *fname) {
206 assert(fname);
207
208 if (++include_level >= MAX_INCLUDE_LEVEL) {
209 fprintf(stderr, "Error ids: maximum include level for config files exceeded\n");
210 exit(1);
211 }
212
213 // make sure the file is owned by root
214 struct stat s;
215 if (stat(fname, &s)) {
216 if (include_level == 1) {
217 fprintf(stderr, "Error ids: config file not found\n");
218 exit(1);
219 }
220 return;
221 }
222 if (s.st_uid || s.st_gid) {
223 fprintf(stderr, "Error ids: config file not owned by root\n");
224 exit(1);
225 }
226
227 fprintf(stderr, "Loading %s config file\n", fname);
228 FILE *fp = fopen(fname, "r");
229 if (!fp) {
230 fprintf(stderr, "Error fids: cannot open config file %s\n", fname);
231 exit(1);
232 }
233
234 char buf[MAXBUF];
235 int line = 0;
236 while (fgets(buf, MAXBUF, fp)) {
237 line++;
238
239 // trim \n
240 char *ptr = strchr(buf, '\n');
241 if (ptr)
242 *ptr = '\0';
243
244 // comments
245 ptr = strchr(buf, '#');
246 if (ptr)
247 *ptr = '\0';
248
249 // empty space
250 ptr = buf;
251 while (*ptr == ' ' || *ptr == '\t')
252 ptr++;
253 char *start = ptr;
254
255 // empty line
256 if (*start == '\0')
257 continue;
258
259 // trailing spaces
260 ptr = start + strlen(start);
261 ptr--;
262 while (*ptr == ' ' || *ptr == '\t')
263 *ptr-- = '\0';
264
265 // replace ${HOME}
266 if (strncmp(start, "include", 7) == 0) {
267 ptr = start + 7;
268 if ((*ptr != ' ' && *ptr != '\t') || *ptr == '\0') {
269 fprintf(stderr, "Error fids: invalid line %d in %s\n", line, fname);
270 exit(1);
271 }
272 while (*ptr == ' ' || *ptr == '\t')
273 ptr++;
274
275 if (*ptr == '/')
276 process_config(ptr);
277 else {
278 // assume the file is in /etc/firejail
279 char *tmp;
280 if (asprintf(&tmp, "/etc/firejail/%s", ptr) == -1)
281 errExit("asprintf");
282 process_config(tmp);
283 free(tmp);
284 }
285 }
286 else if (*start == '!') {
287 // exclude file or dir
288 start++;
289 if (strncmp(start, "${HOME}", 7))
290 db_exclude_add(start);
291 else {
292 char *fname;
293 if (asprintf(&fname, "%s%s", arg_homedir, start + 7) == -1)
294 errExit("asprintf");
295 db_exclude_add(fname);
296 free(fname);
297 }
298 }
299 else if (strncmp(start, "${HOME}", 7))
300 globbing(start);
301 else {
302 char *fname;
303 if (asprintf(&fname, "%s%s", arg_homedir, start + 7) == -1)
304 errExit("asprintf");
305 globbing(fname);
306 free(fname);
307 }
308 }
309
310 fclose(fp);
311 include_level--;
312}
313
314
315
316void usage(void) {
317 printf("Usage: fids [--help|-h|-?] --init|--check homedir\n");
318}
319
320int main(int argc, char **argv) {
321 int i;
322 for (i = 1; i < argc; i++) {
323 if (strcmp(argv[i], "-h") == 0 ||
324 strcmp(argv[i], "-?") == 0 ||
325 strcmp(argv[i], "--help") == 0) {
326 usage();
327 return 0;
328 }
329 else if (strcmp(argv[i], "--init") == 0)
330 arg_init = 1;
331 else if (strcmp(argv[i], "--check") == 0)
332 arg_check = 1;
333 else if (strncmp(argv[i], "--", 2) == 0) {
334 fprintf(stderr, "Error fids: invalid argument %s\n", argv[i]);
335 exit(1);
336 }
337 }
338
339 if (argc != 3) {
340 fprintf(stderr, "Error fids: invalid number of arguments\n");
341 exit(1);
342 }
343 arg_homedir = argv[2];
344
345 int op = arg_check + arg_init;
346 if (op == 0 || op == 2) {
347 fprintf(stderr, "Error fids: use either --init or --check\n");
348 exit(1);
349 }
350
351 if (arg_init) {
352 process_config(SYSCONFDIR"/ids.config");
353 fprintf(stderr, "\n%d files scanned\n", f_scanned);
354 fprintf(stderr, "IDS database initialized\n");
355 }
356 else if (arg_check) {
357 if (db_init()) {
358 fprintf(stderr, "Error: IDS database not initialized, please run \"firejail --ids-init\"\n");
359 exit(1);
360 }
361
362 process_config(SYSCONFDIR"/ids.config");
363 fprintf(stderr, "\n%d files scanned: modified %d, permissions %d, new %d, removed %d\n",
364 f_scanned, f_modified, f_permissions, f_new, f_removed);
365 db_missing();
366 }
367 else
368 assert(0);
369
370 return 0;
371}