diff options
Diffstat (limited to 'src/fids/main.c')
-rw-r--r-- | src/fids/main.c | 370 |
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 | |||
31 | static int dir_level = 1; | ||
32 | static int include_level = 0; | ||
33 | int arg_init = 0; | ||
34 | int arg_check = 0; | ||
35 | char *arg_homedir = NULL; | ||
36 | char *arg_dbfile = NULL; | ||
37 | |||
38 | int f_scanned = 0; | ||
39 | int f_modified = 0; | ||
40 | int f_new = 0; | ||
41 | int f_removed = 0; | ||
42 | int f_permissions = 0; | ||
43 | |||
44 | |||
45 | |||
46 | static 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 | |||
56 | static 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 | ||
65 | static 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 | |||
87 | static 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 | |||
141 | void 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 | |||
179 | void 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 | |||
204 | static 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 | |||
315 | void usage(void) { | ||
316 | printf("Usage: fids [--help|-h|-?] --init|--check homedir\n"); | ||
317 | } | ||
318 | |||
319 | int 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 | } | ||