chiark / gitweb /
condition: unify condition logic in one file
[elogind.git] / src / shared / condition-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/statvfs.h>
27 #include <fnmatch.h>
28
29 #include "sd-id128.h"
30 #include "util.h"
31 #include "condition-util.h"
32 #include "virt.h"
33 #include "path-util.h"
34 #include "fileio.h"
35 #include "unit.h"
36 #include "architecture.h"
37 #include "virt.h"
38 #include "smack-util.h"
39 #include "apparmor-util.h"
40 #include "ima-util.h"
41 #include "selinux-util.h"
42 #include "audit.h"
43
44 Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
45         Condition *c;
46         int r;
47
48         assert(type < _CONDITION_TYPE_MAX);
49
50         c = new0(Condition, 1);
51         if (!c)
52                 return NULL;
53
54         c->type = type;
55         c->trigger = trigger;
56         c->negate = negate;
57
58         r = free_and_strdup(&c->parameter, parameter);
59         if (r < 0) {
60                 free(c);
61                 return NULL;
62         }
63
64         return c;
65 }
66
67 void condition_free(Condition *c) {
68         assert(c);
69
70         free(c->parameter);
71         free(c);
72 }
73
74 void condition_free_list(Condition *first) {
75         Condition *c, *n;
76
77         LIST_FOREACH_SAFE(conditions, c, n, first)
78                 condition_free(c);
79 }
80
81 int condition_test_kernel_command_line(Condition *c) {
82         _cleanup_free_ char *line = NULL;
83         const char *p;
84         bool equal;
85         int r;
86
87         assert(c);
88         assert(c->parameter);
89         assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
90
91         r = proc_cmdline(&line);
92         if (r < 0)
93                 return r;
94         if (r == 0)
95                 return c->negate;
96
97         equal = !!strchr(c->parameter, '=');
98         p = line;
99
100         for (;;) {
101                 _cleanup_free_ char *word = NULL;
102                 bool found;
103
104                 r = unquote_first_word(&p, &word);
105                 if (r < 0)
106                         return r;
107                 if (r == 0)
108                         return c->negate;
109
110                 if (equal)
111                         found = streq(word, c->parameter);
112                 else {
113                         const char *f;
114
115                         f = startswith(word, c->parameter);
116                         found = f && (*f == '=' || *f == 0);
117                 }
118
119                 if (found)
120                         return !c->negate;
121         }
122
123         return c->negate;
124 }
125
126 int condition_test_virtualization(Condition *c) {
127         int b, v;
128         const char *id;
129
130         assert(c);
131         assert(c->parameter);
132         assert(c->type == CONDITION_VIRTUALIZATION);
133
134         v = detect_virtualization(&id);
135         if (v < 0)
136                 return v;
137
138         /* First, compare with yes/no */
139         b = parse_boolean(c->parameter);
140
141         if (v > 0 && b > 0)
142                 return !c->negate;
143
144         if (v == 0 && b == 0)
145                 return !c->negate;
146
147         /* Then, compare categorization */
148         if (v == VIRTUALIZATION_VM && streq(c->parameter, "vm"))
149                 return !c->negate;
150
151         if (v == VIRTUALIZATION_CONTAINER && streq(c->parameter, "container"))
152                 return !c->negate;
153
154         /* Finally compare id */
155         return (v > 0 && streq(c->parameter, id)) == !c->negate;
156 }
157
158 int condition_test_architecture(Condition *c) {
159         int a, b;
160
161         assert(c);
162         assert(c->parameter);
163         assert(c->type == CONDITION_ARCHITECTURE);
164
165         a = uname_architecture();
166         if (a < 0)
167                 return a;
168
169         if (streq(c->parameter, "native"))
170                 b = native_architecture();
171         else
172                 b = architecture_from_string(c->parameter);
173         if (b < 0)
174                 return b;
175
176         return (a == b) == !c->negate;
177 }
178
179 int condition_test_host(Condition *c) {
180         _cleanup_free_ char *h = NULL;
181         sd_id128_t x, y;
182         int r;
183
184         assert(c);
185         assert(c->parameter);
186         assert(c->type == CONDITION_HOST);
187
188         if (sd_id128_from_string(c->parameter, &x) >= 0) {
189
190                 r = sd_id128_get_machine(&y);
191                 if (r < 0)
192                         return r;
193
194                 return sd_id128_equal(x, y) == !c->negate;
195         }
196
197         h = gethostname_malloc();
198         if (!h)
199                 return -ENOMEM;
200
201         return (fnmatch(c->parameter, h, FNM_CASEFOLD) == 0) == !c->negate;
202 }
203
204 int condition_test_ac_power(Condition *c) {
205         int r;
206
207         assert(c);
208         assert(c->parameter);
209         assert(c->type == CONDITION_AC_POWER);
210
211         r = parse_boolean(c->parameter);
212         if (r < 0)
213                 return r;
214
215         return ((on_ac_power() != 0) == !!r) == !c->negate;
216 }
217
218 static int condition_test_security(Condition *c) {
219         assert(c);
220         assert(c->parameter);
221         assert(c->type == CONDITION_SECURITY);
222
223         if (streq(c->parameter, "selinux"))
224                 return mac_selinux_use() == !c->negate;
225         if (streq(c->parameter, "smack"))
226                 return mac_smack_use() == !c->negate;
227         if (streq(c->parameter, "apparmor"))
228                 return mac_apparmor_use() == !c->negate;
229         if (streq(c->parameter, "audit"))
230                 return use_audit() == !c->negate;
231         if (streq(c->parameter, "ima"))
232                 return use_ima() == !c->negate;
233
234         return c->negate;
235 }
236
237 static int condition_test_capability(Condition *c) {
238         _cleanup_fclose_ FILE *f = NULL;
239         cap_value_t value;
240         char line[LINE_MAX];
241         unsigned long long capabilities = -1;
242
243         assert(c);
244         assert(c->parameter);
245         assert(c->type == CONDITION_CAPABILITY);
246
247         /* If it's an invalid capability, we don't have it */
248
249         if (cap_from_name(c->parameter, &value) < 0)
250                 return -EINVAL;
251
252         /* If it's a valid capability we default to assume
253          * that we have it */
254
255         f = fopen("/proc/self/status", "re");
256         if (!f)
257                 return -errno;
258
259         while (fgets(line, sizeof(line), f)) {
260                 truncate_nl(line);
261
262                 if (startswith(line, "CapBnd:")) {
263                         (void) sscanf(line+7, "%llx", &capabilities);
264                         break;
265                 }
266         }
267
268         return !!(capabilities & (1ULL << value)) == !c->negate;
269 }
270
271 static int condition_test_needs_update(Condition *c) {
272         const char *p;
273         struct stat usr, other;
274
275         assert(c);
276         assert(c->parameter);
277         assert(c->type == CONDITION_NEEDS_UPDATE);
278
279         /* If the file system is read-only we shouldn't suggest an update */
280         if (path_is_read_only_fs(c->parameter) > 0)
281                 return c->negate;
282
283         /* Any other failure means we should allow the condition to be true,
284          * so that we rather invoke too many update tools then too
285          * few. */
286
287         if (!path_is_absolute(c->parameter))
288                 return !c->negate;
289
290         p = strappenda(c->parameter, "/.updated");
291         if (lstat(p, &other) < 0)
292                 return !c->negate;
293
294         if (lstat("/usr/", &usr) < 0)
295                 return !c->negate;
296
297         return (usr.st_mtim.tv_sec > other.st_mtim.tv_sec ||
298                 (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec)) == !c->negate;
299 }
300
301 static int condition_test_first_boot(Condition *c) {
302         int r;
303
304         assert(c);
305         assert(c->parameter);
306         assert(c->type == CONDITION_FIRST_BOOT);
307
308         r = parse_boolean(c->parameter);
309         if (r < 0)
310                 return r;
311
312         return ((access("/run/systemd/first-boot", F_OK) >= 0) == !!r) == !c->negate;
313 }
314
315 static int condition_test_path_exists(Condition *c) {
316         assert(c);
317         assert(c->parameter);
318         assert(c->type == CONDITION_PATH_EXISTS);
319
320         return (access(c->parameter, F_OK) >= 0) == !c->negate;
321 }
322
323 static int condition_test_path_exists_glob(Condition *c) {
324         assert(c);
325         assert(c->parameter);
326         assert(c->type == CONDITION_PATH_EXISTS_GLOB);
327
328         return (glob_exists(c->parameter) > 0) == !c->negate;
329 }
330
331 static int condition_test_path_is_directory(Condition *c) {
332         assert(c);
333         assert(c->parameter);
334         assert(c->type == CONDITION_PATH_IS_DIRECTORY);
335
336         return (is_dir(c->parameter, true) > 0) == !c->negate;
337 }
338
339 static int condition_test_path_is_symbolic_link(Condition *c) {
340         assert(c);
341         assert(c->parameter);
342         assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK);
343
344         return (is_symlink(c->parameter) > 0) == !c->negate;
345 }
346
347 static int condition_test_path_is_mount_point(Condition *c) {
348         assert(c);
349         assert(c->parameter);
350         assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
351
352         return (path_is_mount_point(c->parameter, true) > 0) == !c->negate;
353 }
354
355 static int condition_test_path_is_read_write(Condition *c) {
356         assert(c);
357         assert(c->parameter);
358         assert(c->type == CONDITION_PATH_IS_READ_WRITE);
359
360         return (path_is_read_only_fs(c->parameter) > 0) == c->negate;
361 }
362
363 static int condition_test_directory_not_empty(Condition *c) {
364         int r;
365
366         assert(c);
367         assert(c->parameter);
368         assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
369
370         r = dir_is_empty(c->parameter);
371         return !(r == -ENOENT || r > 0) == !c->negate;
372 }
373
374 static int condition_test_file_not_empty(Condition *c) {
375         struct stat st;
376
377         assert(c);
378         assert(c->parameter);
379         assert(c->type == CONDITION_FILE_NOT_EMPTY);
380
381         return (stat(c->parameter, &st) >= 0 &&
382                 S_ISREG(st.st_mode) &&
383                 st.st_size > 0) == !c->negate;
384 }
385
386 static int condition_test_file_is_executable(Condition *c) {
387         struct stat st;
388
389         assert(c);
390         assert(c->parameter);
391         assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
392
393         return (stat(c->parameter, &st) >= 0 &&
394                 S_ISREG(st.st_mode) &&
395                 (st.st_mode & 0111)) == !c->negate;
396 }
397
398 static int condition_test_null(Condition *c) {
399         assert(c);
400         assert(c->parameter);
401         assert(c->type == CONDITION_NULL);
402
403         /* Note that during parsing we already evaluate the string and
404          * store it in c->negate */
405         return !c->negate;
406 }
407
408 int condition_test(Condition *c) {
409
410         static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = {
411                 [CONDITION_PATH_EXISTS] = condition_test_path_exists,
412                 [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob,
413                 [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory,
414                 [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link,
415                 [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point,
416                 [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write,
417                 [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty,
418                 [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty,
419                 [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable,
420                 [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line,
421                 [CONDITION_VIRTUALIZATION] = condition_test_virtualization,
422                 [CONDITION_SECURITY] = condition_test_security,
423                 [CONDITION_CAPABILITY] = condition_test_capability,
424                 [CONDITION_HOST] = condition_test_host,
425                 [CONDITION_AC_POWER] = condition_test_ac_power,
426                 [CONDITION_ARCHITECTURE] = condition_test_architecture,
427                 [CONDITION_NEEDS_UPDATE] = condition_test_needs_update,
428                 [CONDITION_FIRST_BOOT] = condition_test_first_boot,
429                 [CONDITION_NULL] = condition_test_null,
430         };
431
432         assert(c);
433         assert(c->type >= 0);
434         assert(c->type < _CONDITION_TYPE_MAX);
435
436         return condition_tests[c->type](c);
437 }
438
439 void condition_dump(Condition *c, FILE *f, const char *prefix) {
440         assert(c);
441         assert(f);
442
443         if (!prefix)
444                 prefix = "";
445
446         fprintf(f,
447                 "%s\t%s: %s%s%s %s\n",
448                 prefix,
449                 condition_type_to_string(c->type),
450                 c->trigger ? "|" : "",
451                 c->negate ? "!" : "",
452                 c->parameter,
453                 CONDITION_STATE_IS_FAILED(c->state) ? "failed" : CONDITION_STATE_IS_SUCCEEDED(c->state) ? "succeeded" : "untested");
454 }
455
456 void condition_dump_list(Condition *first, FILE *f, const char *prefix) {
457         Condition *c;
458
459         LIST_FOREACH(conditions, c, first)
460                 condition_dump(c, f, prefix);
461 }
462
463 static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
464         [CONDITION_PATH_EXISTS] = "ConditionPathExists",
465         [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
466         [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
467         [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
468         [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
469         [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
470         [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
471         [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
472         [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
473         [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
474         [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
475         [CONDITION_SECURITY] = "ConditionSecurity",
476         [CONDITION_CAPABILITY] = "ConditionCapability",
477         [CONDITION_HOST] = "ConditionHost",
478         [CONDITION_AC_POWER] = "ConditionACPower",
479         [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
480         [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate",
481         [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
482         [CONDITION_NULL] = "ConditionNull"
483 };
484
485 DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);