]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/condition.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / shared / condition.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <fnmatch.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #include "sd-id128.h"
33
34 #include "alloc-util.h"
35 #include "apparmor-util.h"
36 #include "architecture.h"
37 #include "audit-util.h"
38 #include "cap-list.h"
39 #include "condition.h"
40 #include "extract-word.h"
41 #include "fd-util.h"
42 #include "fileio.h"
43 #include "glob-util.h"
44 #include "hostname-util.h"
45 #include "ima-util.h"
46 #include "list.h"
47 #include "macro.h"
48 #include "mount-util.h"
49 #include "parse-util.h"
50 #include "path-util.h"
51 #include "proc-cmdline.h"
52 #include "process-util.h"
53 #include "selinux-util.h"
54 #include "smack-util.h"
55 #include "stat-util.h"
56 #include "string-table.h"
57 #include "string-util.h"
58 #include "tomoyo-util.h"
59 #include "user-util.h"
60 #include "util.h"
61 #include "virt.h"
62
63 Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
64 Condition *c;
65 int r;
66
67 assert(type >= 0);
68 assert(type < _CONDITION_TYPE_MAX);
69 assert((!parameter) == (type == CONDITION_NULL));
70
71 c = new0(Condition, 1);
72 if (!c)
73 return NULL;
74
75 c->type = type;
76 c->trigger = trigger;
77 c->negate = negate;
78
79 r = free_and_strdup(&c->parameter, parameter);
80 if (r < 0) {
81 free(c);
82 return NULL;
83 }
84
85 return c;
86 }
87
88 void condition_free(Condition *c) {
89 assert(c);
90
91 free(c->parameter);
92 free(c);
93 }
94
95 Condition* condition_free_list(Condition *first) {
96 Condition *c, *n;
97
98 LIST_FOREACH_SAFE(conditions, c, n, first)
99 condition_free(c);
100
101 return NULL;
102 }
103
104 static int condition_test_kernel_command_line(Condition *c) {
105 _cleanup_free_ char *line = NULL;
106 const char *p;
107 bool equal;
108 int r;
109
110 assert(c);
111 assert(c->parameter);
112 assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
113
114 r = proc_cmdline(&line);
115 if (r < 0)
116 return r;
117
118 equal = !!strchr(c->parameter, '=');
119
120 for (p = line;;) {
121 _cleanup_free_ char *word = NULL;
122 bool found;
123
124 r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX);
125 if (r < 0)
126 return r;
127 if (r == 0)
128 break;
129
130 if (equal)
131 found = streq(word, c->parameter);
132 else {
133 const char *f;
134
135 f = startswith(word, c->parameter);
136 found = f && IN_SET(*f, 0, '=');
137 }
138
139 if (found)
140 return true;
141 }
142
143 return false;
144 }
145
146 static int condition_test_user(Condition *c) {
147 uid_t id;
148 int r;
149 _cleanup_free_ char *username = NULL;
150 const char *u;
151
152 assert(c);
153 assert(c->parameter);
154 assert(c->type == CONDITION_USER);
155
156 r = parse_uid(c->parameter, &id);
157 if (r >= 0)
158 return id == getuid() || id == geteuid();
159
160 if (streq("@system", c->parameter))
161 return getuid() <= SYSTEM_UID_MAX || geteuid() <= SYSTEM_UID_MAX;
162
163 username = getusername_malloc();
164 if (!username)
165 return -ENOMEM;
166
167 if (streq(username, c->parameter))
168 return 1;
169
170 if (getpid_cached() == 1)
171 return streq(c->parameter, "root");
172
173 u = c->parameter;
174 r = get_user_creds(&u, &id, NULL, NULL, NULL);
175 if (r < 0)
176 return 0;
177
178 return id == getuid() || id == geteuid();
179 }
180
181 static int condition_test_group(Condition *c) {
182 gid_t id;
183 int r;
184
185 assert(c);
186 assert(c->parameter);
187 assert(c->type == CONDITION_GROUP);
188
189 r = parse_gid(c->parameter, &id);
190 if (r >= 0)
191 return in_gid(id);
192
193 /* Avoid any NSS lookups if we are PID1 */
194 if (getpid_cached() == 1)
195 return streq(c->parameter, "root");
196
197 return in_group(c->parameter) > 0;
198 }
199
200 static int condition_test_virtualization(Condition *c) {
201 int b, v;
202
203 assert(c);
204 assert(c->parameter);
205 assert(c->type == CONDITION_VIRTUALIZATION);
206
207 if (streq(c->parameter, "private-users"))
208 return running_in_userns();
209
210 v = detect_virtualization();
211 if (v < 0)
212 return v;
213
214 /* First, compare with yes/no */
215 b = parse_boolean(c->parameter);
216 if (b >= 0)
217 return b == !!v;
218
219 /* Then, compare categorization */
220 if (streq(c->parameter, "vm"))
221 return VIRTUALIZATION_IS_VM(v);
222
223 if (streq(c->parameter, "container"))
224 return VIRTUALIZATION_IS_CONTAINER(v);
225
226 /* Finally compare id */
227 return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v));
228 }
229
230 static int condition_test_architecture(Condition *c) {
231 int a, b;
232
233 assert(c);
234 assert(c->parameter);
235 assert(c->type == CONDITION_ARCHITECTURE);
236
237 a = uname_architecture();
238 if (a < 0)
239 return a;
240
241 if (streq(c->parameter, "native"))
242 b = native_architecture();
243 else {
244 b = architecture_from_string(c->parameter);
245 if (b < 0) /* unknown architecture? Then it's definitely not ours */
246 return false;
247 }
248
249 return a == b;
250 }
251
252 static int condition_test_host(Condition *c) {
253 _cleanup_free_ char *h = NULL;
254 sd_id128_t x, y;
255 int r;
256
257 assert(c);
258 assert(c->parameter);
259 assert(c->type == CONDITION_HOST);
260
261 if (sd_id128_from_string(c->parameter, &x) >= 0) {
262
263 r = sd_id128_get_machine(&y);
264 if (r < 0)
265 return r;
266
267 return sd_id128_equal(x, y);
268 }
269
270 h = gethostname_malloc();
271 if (!h)
272 return -ENOMEM;
273
274 return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0;
275 }
276
277 static int condition_test_ac_power(Condition *c) {
278 int r;
279
280 assert(c);
281 assert(c->parameter);
282 assert(c->type == CONDITION_AC_POWER);
283
284 r = parse_boolean(c->parameter);
285 if (r < 0)
286 return r;
287
288 return (on_ac_power() != 0) == !!r;
289 }
290
291 static int condition_test_security(Condition *c) {
292 assert(c);
293 assert(c->parameter);
294 assert(c->type == CONDITION_SECURITY);
295
296 if (streq(c->parameter, "selinux"))
297 return mac_selinux_use();
298 if (streq(c->parameter, "smack"))
299 return mac_smack_use();
300 if (streq(c->parameter, "apparmor"))
301 return mac_apparmor_use();
302 if (streq(c->parameter, "audit"))
303 return use_audit();
304 if (streq(c->parameter, "ima"))
305 return use_ima();
306 if (streq(c->parameter, "tomoyo"))
307 return mac_tomoyo_use();
308
309 return false;
310 }
311
312 static int condition_test_capability(Condition *c) {
313 _cleanup_fclose_ FILE *f = NULL;
314 int value;
315 char line[LINE_MAX];
316 unsigned long long capabilities = -1;
317
318 assert(c);
319 assert(c->parameter);
320 assert(c->type == CONDITION_CAPABILITY);
321
322 /* If it's an invalid capability, we don't have it */
323 value = capability_from_name(c->parameter);
324 if (value < 0)
325 return -EINVAL;
326
327 /* If it's a valid capability we default to assume
328 * that we have it */
329
330 f = fopen("/proc/self/status", "re");
331 if (!f)
332 return -errno;
333
334 while (fgets(line, sizeof(line), f)) {
335 truncate_nl(line);
336
337 if (startswith(line, "CapBnd:")) {
338 (void) sscanf(line+7, "%llx", &capabilities);
339 break;
340 }
341 }
342
343 return !!(capabilities & (1ULL << value));
344 }
345
346 static int condition_test_needs_update(Condition *c) {
347 const char *p;
348 struct stat usr, other;
349
350 assert(c);
351 assert(c->parameter);
352 assert(c->type == CONDITION_NEEDS_UPDATE);
353
354 /* If the file system is read-only we shouldn't suggest an update */
355 if (path_is_read_only_fs(c->parameter) > 0)
356 return false;
357
358 /* Any other failure means we should allow the condition to be true,
359 * so that we rather invoke too many update tools than too
360 * few. */
361
362 if (!path_is_absolute(c->parameter))
363 return true;
364
365 p = strjoina(c->parameter, "/.updated");
366 if (lstat(p, &other) < 0)
367 return true;
368
369 if (lstat("/usr/", &usr) < 0)
370 return true;
371
372 /*
373 * First, compare seconds as they are always accurate...
374 */
375 if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec)
376 return usr.st_mtim.tv_sec > other.st_mtim.tv_sec;
377
378 /*
379 * ...then compare nanoseconds.
380 *
381 * A false positive is only possible when /usr's nanoseconds > 0
382 * (otherwise /usr cannot be strictly newer than the target file)
383 * AND the target file's nanoseconds == 0
384 * (otherwise the filesystem supports nsec timestamps, see stat(2)).
385 */
386 if (usr.st_mtim.tv_nsec > 0 && other.st_mtim.tv_nsec == 0) {
387 _cleanup_free_ char *timestamp_str = NULL;
388 uint64_t timestamp;
389 int r;
390
391 r = parse_env_file(p, NULL, "TIMESTAMP_NSEC", &timestamp_str, NULL);
392 if (r < 0) {
393 log_error_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p);
394 return true;
395 } else if (r == 0) {
396 log_debug("No data in timestamp file '%s', using mtime", p);
397 return true;
398 }
399
400 r = safe_atou64(timestamp_str, &timestamp);
401 if (r < 0) {
402 log_error_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p);
403 return true;
404 }
405
406 timespec_store(&other.st_mtim, timestamp);
407 }
408
409 return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec;
410 }
411
412 static int condition_test_first_boot(Condition *c) {
413 int r;
414
415 assert(c);
416 assert(c->parameter);
417 assert(c->type == CONDITION_FIRST_BOOT);
418
419 r = parse_boolean(c->parameter);
420 if (r < 0)
421 return r;
422
423 return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r;
424 }
425
426 static int condition_test_path_exists(Condition *c) {
427 assert(c);
428 assert(c->parameter);
429 assert(c->type == CONDITION_PATH_EXISTS);
430
431 return access(c->parameter, F_OK) >= 0;
432 }
433
434 static int condition_test_path_exists_glob(Condition *c) {
435 assert(c);
436 assert(c->parameter);
437 assert(c->type == CONDITION_PATH_EXISTS_GLOB);
438
439 return glob_exists(c->parameter) > 0;
440 }
441
442 static int condition_test_path_is_directory(Condition *c) {
443 assert(c);
444 assert(c->parameter);
445 assert(c->type == CONDITION_PATH_IS_DIRECTORY);
446
447 return is_dir(c->parameter, true) > 0;
448 }
449
450 static int condition_test_path_is_symbolic_link(Condition *c) {
451 assert(c);
452 assert(c->parameter);
453 assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK);
454
455 return is_symlink(c->parameter) > 0;
456 }
457
458 static int condition_test_path_is_mount_point(Condition *c) {
459 assert(c);
460 assert(c->parameter);
461 assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
462
463 return path_is_mount_point(c->parameter, NULL, AT_SYMLINK_FOLLOW) > 0;
464 }
465
466 static int condition_test_path_is_read_write(Condition *c) {
467 assert(c);
468 assert(c->parameter);
469 assert(c->type == CONDITION_PATH_IS_READ_WRITE);
470
471 return path_is_read_only_fs(c->parameter) <= 0;
472 }
473
474 static int condition_test_directory_not_empty(Condition *c) {
475 int r;
476
477 assert(c);
478 assert(c->parameter);
479 assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
480
481 r = dir_is_empty(c->parameter);
482 return r <= 0 && r != -ENOENT;
483 }
484
485 static int condition_test_file_not_empty(Condition *c) {
486 struct stat st;
487
488 assert(c);
489 assert(c->parameter);
490 assert(c->type == CONDITION_FILE_NOT_EMPTY);
491
492 return (stat(c->parameter, &st) >= 0 &&
493 S_ISREG(st.st_mode) &&
494 st.st_size > 0);
495 }
496
497 static int condition_test_file_is_executable(Condition *c) {
498 struct stat st;
499
500 assert(c);
501 assert(c->parameter);
502 assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
503
504 return (stat(c->parameter, &st) >= 0 &&
505 S_ISREG(st.st_mode) &&
506 (st.st_mode & 0111));
507 }
508
509 static int condition_test_null(Condition *c) {
510 assert(c);
511 assert(c->type == CONDITION_NULL);
512
513 /* Note that during parsing we already evaluate the string and
514 * store it in c->negate */
515 return true;
516 }
517
518 int condition_test(Condition *c) {
519
520 static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = {
521 [CONDITION_PATH_EXISTS] = condition_test_path_exists,
522 [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob,
523 [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory,
524 [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link,
525 [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point,
526 [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write,
527 [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty,
528 [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty,
529 [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable,
530 [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line,
531 [CONDITION_VIRTUALIZATION] = condition_test_virtualization,
532 [CONDITION_SECURITY] = condition_test_security,
533 [CONDITION_CAPABILITY] = condition_test_capability,
534 [CONDITION_HOST] = condition_test_host,
535 [CONDITION_AC_POWER] = condition_test_ac_power,
536 [CONDITION_ARCHITECTURE] = condition_test_architecture,
537 [CONDITION_NEEDS_UPDATE] = condition_test_needs_update,
538 [CONDITION_FIRST_BOOT] = condition_test_first_boot,
539 [CONDITION_USER] = condition_test_user,
540 [CONDITION_GROUP] = condition_test_group,
541 [CONDITION_NULL] = condition_test_null,
542 };
543
544 int r, b;
545
546 assert(c);
547 assert(c->type >= 0);
548 assert(c->type < _CONDITION_TYPE_MAX);
549
550 r = condition_tests[c->type](c);
551 if (r < 0) {
552 c->result = CONDITION_ERROR;
553 return r;
554 }
555
556 b = (r > 0) == !c->negate;
557 c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED;
558 return b;
559 }
560
561 void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) {
562 assert(c);
563 assert(f);
564
565 if (!prefix)
566 prefix = "";
567
568 fprintf(f,
569 "%s\t%s: %s%s%s %s\n",
570 prefix,
571 to_string(c->type),
572 c->trigger ? "|" : "",
573 c->negate ? "!" : "",
574 c->parameter,
575 condition_result_to_string(c->result));
576 }
577
578 void condition_dump_list(Condition *first, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) {
579 Condition *c;
580
581 LIST_FOREACH(conditions, c, first)
582 condition_dump(c, f, prefix, to_string);
583 }
584
585 static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
586 [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
587 [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
588 [CONDITION_HOST] = "ConditionHost",
589 [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
590 [CONDITION_SECURITY] = "ConditionSecurity",
591 [CONDITION_CAPABILITY] = "ConditionCapability",
592 [CONDITION_AC_POWER] = "ConditionACPower",
593 [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate",
594 [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
595 [CONDITION_PATH_EXISTS] = "ConditionPathExists",
596 [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
597 [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
598 [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
599 [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
600 [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
601 [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
602 [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
603 [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
604 [CONDITION_USER] = "ConditionUser",
605 [CONDITION_GROUP] = "ConditionGroup",
606 [CONDITION_NULL] = "ConditionNull"
607 };
608
609 DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
610
611 static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
612 [CONDITION_ARCHITECTURE] = "AssertArchitecture",
613 [CONDITION_VIRTUALIZATION] = "AssertVirtualization",
614 [CONDITION_HOST] = "AssertHost",
615 [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
616 [CONDITION_SECURITY] = "AssertSecurity",
617 [CONDITION_CAPABILITY] = "AssertCapability",
618 [CONDITION_AC_POWER] = "AssertACPower",
619 [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate",
620 [CONDITION_FIRST_BOOT] = "AssertFirstBoot",
621 [CONDITION_PATH_EXISTS] = "AssertPathExists",
622 [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob",
623 [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory",
624 [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink",
625 [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint",
626 [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite",
627 [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
628 [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
629 [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",
630 [CONDITION_USER] = "AssertUser",
631 [CONDITION_GROUP] = "AssertGroup",
632 [CONDITION_NULL] = "AssertNull"
633 };
634
635 DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
636
637 static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
638 [CONDITION_UNTESTED] = "untested",
639 [CONDITION_SUCCEEDED] = "succeeded",
640 [CONDITION_FAILED] = "failed",
641 [CONDITION_ERROR] = "error",
642 };
643
644 DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult);