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