chiark / gitweb /
bus-proxy: properly index policy by uid/gid when parsing
[elogind.git] / src / bus-proxyd / bus-policy.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 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 "xml.h"
23 #include "fileio.h"
24 #include "strv.h"
25 #include "conf-files.h"
26 #include "bus-internal.h"
27 #include "bus-policy.h"
28
29 static void policy_item_free(PolicyItem *i) {
30         assert(i);
31
32         free(i->interface);
33         free(i->member);
34         free(i->error);
35         free(i->name);
36         free(i->path);
37         free(i);
38 }
39
40 DEFINE_TRIVIAL_CLEANUP_FUNC(PolicyItem*, policy_item_free);
41
42 static int file_load(Policy *p, const char *path) {
43
44         _cleanup_free_ char *c = NULL, *policy_user = NULL, *policy_group = NULL;
45         _cleanup_(policy_item_freep) PolicyItem *i = NULL;
46         void *xml_state = NULL;
47         unsigned n_other = 0;
48         const char *q;
49         int r;
50
51         enum {
52                 STATE_OUTSIDE,
53                 STATE_BUSCONFIG,
54                 STATE_POLICY,
55                 STATE_POLICY_CONTEXT,
56                 STATE_POLICY_USER,
57                 STATE_POLICY_GROUP,
58                 STATE_POLICY_OTHER_ATTRIBUTE,
59                 STATE_ALLOW_DENY,
60                 STATE_ALLOW_DENY_INTERFACE,
61                 STATE_ALLOW_DENY_MEMBER,
62                 STATE_ALLOW_DENY_ERROR,
63                 STATE_ALLOW_DENY_PATH,
64                 STATE_ALLOW_DENY_MESSAGE_TYPE,
65                 STATE_ALLOW_DENY_NAME,
66                 STATE_ALLOW_DENY_OTHER_ATTRIBUTE,
67                 STATE_OTHER,
68         } state = STATE_OUTSIDE;
69
70         enum {
71                 POLICY_CATEGORY_NONE,
72                 POLICY_CATEGORY_DEFAULT,
73                 POLICY_CATEGORY_MANDATORY,
74                 POLICY_CATEGORY_USER,
75                 POLICY_CATEGORY_GROUP
76         } policy_category = POLICY_CATEGORY_NONE;
77
78         unsigned line = 0;
79
80         assert(p);
81
82         r = read_full_file(path, &c, NULL);
83         if (r < 0) {
84                 if (r == -ENOENT)
85                         return 0;
86                 if (r == -EISDIR)
87                         return r;
88
89                 log_error("Failed to load %s: %s", path, strerror(-r));
90                 return r;
91         }
92
93         q = c;
94         for (;;) {
95                 _cleanup_free_ char *name = NULL;
96                 int t;
97
98                 t = xml_tokenize(&q, &name, &xml_state, &line);
99                 if (t < 0) {
100                         log_error("XML parse failure in %s: %s", path, strerror(-t));
101                         return t;
102                 }
103
104                 switch (state) {
105
106                 case STATE_OUTSIDE:
107
108                         if (t == XML_TAG_OPEN) {
109                                 if (streq(name, "busconfig"))
110                                         state = STATE_BUSCONFIG;
111                                 else {
112                                         log_error("Unexpected tag %s at %s:%u.", name, path, line);
113                                         return -EINVAL;
114                                 }
115
116                         } else if (t == XML_END)
117                                 return 0;
118                         else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
119                                 log_error("Unexpected token (1) at %s:%u.", path, line);
120                                 return -EINVAL;
121                         }
122
123                         break;
124
125                 case STATE_BUSCONFIG:
126
127                         if (t == XML_TAG_OPEN) {
128                                 if (streq(name, "policy")) {
129                                         state = STATE_POLICY;
130                                         policy_category = POLICY_CATEGORY_NONE;
131                                         free(policy_user);
132                                         free(policy_group);
133                                         policy_user = policy_group = NULL;
134                                 } else {
135                                         state = STATE_OTHER;
136                                         n_other = 0;
137                                 }
138                         } else if (t == XML_TAG_CLOSE_EMPTY ||
139                                    (t == XML_TAG_CLOSE && streq(name, "busconfig")))
140                                 state = STATE_OUTSIDE;
141                         else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
142                                 log_error("Unexpected token (2) at %s:%u.", path, line);
143                                 return -EINVAL;
144                         }
145
146                         break;
147
148                 case STATE_POLICY:
149
150                         if (t == XML_ATTRIBUTE_NAME) {
151                                 if (streq(name, "context"))
152                                         state = STATE_POLICY_CONTEXT;
153                                 else if (streq(name, "user"))
154                                         state = STATE_POLICY_USER;
155                                 else if (streq(name, "group"))
156                                         state = STATE_POLICY_GROUP;
157                                 else {
158                                         log_warning("Attribute %s of <policy> tag unknown at %s:%u, ignoring.", name, path, line);
159                                         state = STATE_POLICY_OTHER_ATTRIBUTE;
160                                 }
161                         } else if (t == XML_TAG_CLOSE_EMPTY ||
162                                    (t == XML_TAG_CLOSE && streq(name, "policy")))
163                                 state = STATE_BUSCONFIG;
164                         else if (t == XML_TAG_OPEN) {
165                                 PolicyItemType it;
166
167                                 if (streq(name, "allow"))
168                                         it = POLICY_ITEM_ALLOW;
169                                 else if (streq(name, "deny"))
170                                         it = POLICY_ITEM_DENY;
171                                 else {
172                                         log_warning("Unknown tag %s in <policy> %s:%u.", name, path, line);
173                                         return -EINVAL;
174                                 }
175
176                                 assert(!i);
177                                 i = new0(PolicyItem, 1);
178                                 if (!i)
179                                         return log_oom();
180
181                                 i->type = it;
182                                 state = STATE_ALLOW_DENY;
183
184                         } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
185                                 log_error("Unexpected token (3) at %s:%u.", path, line);
186                                 return -EINVAL;
187                         }
188
189                         break;
190
191                 case STATE_POLICY_CONTEXT:
192
193                         if (t == XML_ATTRIBUTE_VALUE) {
194                                 if (streq(name, "default")) {
195                                         policy_category = POLICY_CATEGORY_DEFAULT;
196                                         state = STATE_POLICY;
197                                 } else if (streq(name, "mandatory")) {
198                                         policy_category = POLICY_CATEGORY_MANDATORY;
199                                         state = STATE_POLICY;
200                                 } else {
201                                         log_error("context= parameter %s unknown for <policy> at %s:%u.", name, path, line);
202                                         return -EINVAL;
203                                 }
204                         } else {
205                                 log_error("Unexpected token (4) at %s:%u.", path, line);
206                                 return -EINVAL;
207                         }
208
209                         break;
210
211                 case STATE_POLICY_USER:
212
213                         if (t == XML_ATTRIBUTE_VALUE) {
214                                 free(policy_user);
215                                 policy_user = name;
216                                 name = NULL;
217                                 policy_category = POLICY_CATEGORY_USER;
218                                 state = STATE_POLICY;
219                         } else {
220                                 log_error("Unexpected token (5) in %s:%u.", path, line);
221                                 return -EINVAL;
222                         }
223
224                         break;
225
226                 case STATE_POLICY_GROUP:
227
228                         if (t == XML_ATTRIBUTE_VALUE) {
229                                 free(policy_group);
230                                 policy_group = name;
231                                 name = NULL;
232                                 policy_category = POLICY_CATEGORY_GROUP;
233                                 state = STATE_POLICY;
234                         } else {
235                                 log_error("Unexpected token (6) at %s:%u.", path, line);
236                                 return -EINVAL;
237                         }
238
239                         break;
240
241                 case STATE_POLICY_OTHER_ATTRIBUTE:
242
243                         if (t == XML_ATTRIBUTE_VALUE)
244                                 state = STATE_POLICY;
245                         else {
246                                 log_error("Unexpected token (7) in %s:%u.", path, line);
247                                 return -EINVAL;
248                         }
249
250                         break;
251
252                 case STATE_ALLOW_DENY:
253
254                         assert(i);
255
256                         if (t == XML_ATTRIBUTE_NAME) {
257                                 PolicyItemClass ic;
258
259                                 if (startswith(name, "send_"))
260                                         ic = POLICY_ITEM_SEND;
261                                 else if (startswith(name, "receive_"))
262                                         ic = POLICY_ITEM_RECV;
263                                 else if (streq(name, "own"))
264                                         ic = POLICY_ITEM_OWN;
265                                 else if (streq(name, "own_prefix"))
266                                         ic = POLICY_ITEM_OWN_PREFIX;
267                                 else if (streq(name, "user"))
268                                         ic = POLICY_ITEM_USER;
269                                 else if (streq(name, "group"))
270                                         ic = POLICY_ITEM_GROUP;
271                                 else {
272                                         log_error("Unknown attribute %s= at %s:%u, ignoring.", name, path, line);
273                                         state = STATE_ALLOW_DENY_OTHER_ATTRIBUTE;
274                                         break;
275                                 }
276
277                                 if (i->class != _POLICY_ITEM_CLASS_UNSET && ic != i->class) {
278                                         log_error("send_ and receive_ fields mixed on same tag at %s:%u.", path, line);
279                                         return -EINVAL;
280                                 }
281
282                                 i->class = ic;
283
284                                 if (ic == POLICY_ITEM_SEND || ic == POLICY_ITEM_RECV) {
285                                         const char *u;
286
287                                         u = strchr(name, '_');
288                                         assert(u);
289
290                                         u++;
291
292                                         if (streq(u, "interface"))
293                                                 state = STATE_ALLOW_DENY_INTERFACE;
294                                         else if (streq(u, "member"))
295                                                 state = STATE_ALLOW_DENY_MEMBER;
296                                         else if (streq(u, "error"))
297                                                 state = STATE_ALLOW_DENY_ERROR;
298                                         else if (streq(u, "path"))
299                                                 state = STATE_ALLOW_DENY_PATH;
300                                         else if (streq(u, "type"))
301                                                 state = STATE_ALLOW_DENY_MESSAGE_TYPE;
302                                         else if ((streq(u, "destination") && ic == POLICY_ITEM_SEND) ||
303                                                  (streq(u, "sender") && ic == POLICY_ITEM_RECV))
304                                                 state = STATE_ALLOW_DENY_NAME;
305                                         else {
306                                                 log_error("Unknown attribute %s= at %s:%u, ignoring.", name, path, line);
307                                                 state = STATE_ALLOW_DENY_OTHER_ATTRIBUTE;
308                                                 break;
309                                         }
310                                 } else
311                                         state = STATE_ALLOW_DENY_NAME;
312
313                         } else if (t == XML_TAG_CLOSE_EMPTY ||
314                                    (t == XML_TAG_CLOSE && streq(name, i->type == POLICY_ITEM_ALLOW ? "allow" : "deny"))) {
315
316                                 if (i->class == _POLICY_ITEM_CLASS_UNSET) {
317                                         log_error("Policy not set at %s:%u.", path, line);
318                                         return -EINVAL;
319                                 }
320
321                                 if (policy_category == POLICY_CATEGORY_DEFAULT)
322                                         LIST_PREPEND(items, p->default_items, i);
323                                 else if (policy_category == POLICY_CATEGORY_MANDATORY)
324                                         LIST_PREPEND(items, p->default_items, i);
325                                 else if (policy_category == POLICY_CATEGORY_USER) {
326                                         const char *u = policy_user;
327
328                                         assert_cc(sizeof(uid_t) == sizeof(uint32_t));
329
330                                         r = hashmap_ensure_allocated(&p->user_items, trivial_hash_func, trivial_compare_func);
331                                         if (r < 0)
332                                                 return log_oom();
333
334                                         if (!u) {
335                                                 log_error("User policy without name");
336                                                 return -EINVAL;
337                                         }
338
339                                         r = get_user_creds(&u, &i->uid, NULL, NULL, NULL);
340                                         if (r < 0) {
341                                                 log_error("Failed to resolve user %s, ignoring policy: %s", u, strerror(-r));
342                                                 free(i);
343                                         } else {
344                                                 PolicyItem *first;
345
346                                                 first = hashmap_get(p->user_items, UINT32_TO_PTR(i->uid));
347                                                 LIST_PREPEND(items, first, i);
348
349                                                 r = hashmap_replace(p->user_items, UINT32_TO_PTR(i->uid), first);
350                                                 if (r < 0) {
351                                                         LIST_REMOVE(items, first, i);
352                                                         return log_oom();
353                                                 }
354                                         }
355
356                                 } else if (policy_category == POLICY_CATEGORY_GROUP) {
357                                         const char *g = policy_group;
358
359                                         assert_cc(sizeof(gid_t) == sizeof(uint32_t));
360
361                                         r = hashmap_ensure_allocated(&p->group_items, trivial_hash_func, trivial_compare_func);
362                                         if (r < 0)
363                                                 return log_oom();
364
365                                         if (!g) {
366                                                 log_error("Group policy without name");
367                                                 return -EINVAL;
368                                         }
369
370                                         r = get_group_creds(&g, &i->gid);
371                                         if (r < 0) {
372                                                 log_error("Failed to resolve group %s, ignoring policy: %s", g, strerror(-r));
373                                                 free(i);
374                                         } else {
375                                                 PolicyItem *first;
376
377                                                 first = hashmap_get(p->group_items, UINT32_TO_PTR(i->gid));
378                                                 LIST_PREPEND(items, first, i);
379
380                                                 r = hashmap_replace(p->group_items, UINT32_TO_PTR(i->gid), first);
381                                                 if (r < 0) {
382                                                         LIST_REMOVE(items, first, i);
383                                                         return log_oom();
384                                                 }
385                                         }
386                                 }
387
388                                 state = STATE_POLICY;
389                                 i = NULL;
390
391                         } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
392                                 log_error("Unexpected token (8) at %s:%u.", path, line);
393                                 return -EINVAL;
394                         }
395
396                         break;
397
398                 case STATE_ALLOW_DENY_INTERFACE:
399
400                         if (t == XML_ATTRIBUTE_VALUE) {
401                                 assert(i);
402                                 if (i->interface) {
403                                         log_error("Duplicate interface at %s:%u.", path, line);
404                                         return -EINVAL;
405                                 }
406
407                                 i->interface = name;
408                                 name = NULL;
409                                 state = STATE_ALLOW_DENY;
410                         } else {
411                                 log_error("Unexpected token (9) at %s:%u.", path, line);
412                                 return -EINVAL;
413                         }
414
415                         break;
416
417                 case STATE_ALLOW_DENY_MEMBER:
418
419                         if (t == XML_ATTRIBUTE_VALUE) {
420                                 assert(i);
421                                 if (i->member) {
422                                         log_error("Duplicate member in %s:%u.", path, line);
423                                         return -EINVAL;
424                                 }
425
426                                 i->member = name;
427                                 name = NULL;
428                                 state = STATE_ALLOW_DENY;
429                         } else {
430                                 log_error("Unexpected token (10) in %s:%u.", path, line);
431                                 return -EINVAL;
432                         }
433
434                         break;
435
436                 case STATE_ALLOW_DENY_ERROR:
437
438                         if (t == XML_ATTRIBUTE_VALUE) {
439                                 assert(i);
440                                 if (i->error) {
441                                         log_error("Duplicate error in %s:%u.", path, line);
442                                         return -EINVAL;
443                                 }
444
445                                 i->error = name;
446                                 name = NULL;
447                                 state = STATE_ALLOW_DENY;
448                         } else {
449                                 log_error("Unexpected token (11) in %s:%u.", path, line);
450                                 return -EINVAL;
451                         }
452
453                         break;
454
455                 case STATE_ALLOW_DENY_PATH:
456
457                         if (t == XML_ATTRIBUTE_VALUE) {
458                                 assert(i);
459                                 if (i->path) {
460                                         log_error("Duplicate path in %s:%u.", path, line);
461                                         return -EINVAL;
462                                 }
463
464                                 i->path = name;
465                                 name = NULL;
466                                 state = STATE_ALLOW_DENY;
467                         } else {
468                                 log_error("Unexpected token (12) in %s:%u.", path, line);
469                                 return -EINVAL;
470                         }
471
472                         break;
473
474                 case STATE_ALLOW_DENY_MESSAGE_TYPE:
475
476                         if (t == XML_ATTRIBUTE_VALUE) {
477                                 assert(i);
478
479                                 if (i->message_type != 0) {
480                                         log_error("Duplicate message type in %s:%u.", path, line);
481                                         return -EINVAL;
482                                 }
483
484                                 r = bus_message_type_from_string(name, &i->message_type);
485                                 if (r < 0) {
486                                         log_error("Invalid message type in %s:%u.", path, line);
487                                         return -EINVAL;
488                                 }
489
490                                 state = STATE_ALLOW_DENY;
491                         } else {
492                                 log_error("Unexpected token (13) in %s:%u.", path, line);
493                                 return -EINVAL;
494                         }
495
496                         break;
497
498                 case STATE_ALLOW_DENY_NAME:
499
500                         if (t == XML_ATTRIBUTE_VALUE) {
501                                 assert(i);
502                                 if (i->name) {
503                                         log_error("Duplicate name in %s:%u.", path, line);
504                                         return -EINVAL;
505                                 }
506
507                                 i->name = name;
508                                 name = NULL;
509                                 state = STATE_ALLOW_DENY;
510                         } else {
511                                 log_error("Unexpected token (14) in %s:%u.", path, line);
512                                 return -EINVAL;
513                         }
514
515                         break;
516
517                 case STATE_ALLOW_DENY_OTHER_ATTRIBUTE:
518
519                         if (t == XML_ATTRIBUTE_VALUE)
520                                 state = STATE_ALLOW_DENY;
521                         else {
522                                 log_error("Unexpected token (15) in %s:%u.", path, line);
523                                 return -EINVAL;
524                         }
525
526                         break;
527
528                 case STATE_OTHER:
529
530                         if (t == XML_TAG_OPEN)
531                                 n_other++;
532                         else if (t == XML_TAG_CLOSE || t == XML_TAG_CLOSE_EMPTY) {
533
534                                 if (n_other == 0)
535                                         state = STATE_BUSCONFIG;
536                                 else
537                                         n_other--;
538                         }
539
540                         break;
541                 }
542         }
543 }
544
545 int policy_load(Policy *p, char **files) {
546         char **i;
547         int r;
548
549         assert(p);
550
551         STRV_FOREACH(i, files) {
552
553                 r = file_load(p, *i);
554                 if (r == -EISDIR) {
555                         _cleanup_strv_free_ char **l = NULL;
556                         char **j;
557
558                         r = conf_files_list(&l, ".conf", NULL, *i, NULL);
559                         if (r < 0) {
560                                 log_error("Failed to get configuration file list: %s", strerror(-r));
561                                 return r;
562                         }
563
564                         STRV_FOREACH(j, l)
565                                 file_load(p, *j);
566                 }
567
568                 /* We ignore all errors but EISDIR, and just proceed. */
569         }
570
571         return 0;
572 }
573
574 void policy_free(Policy *p) {
575         PolicyItem *i, *first;
576
577         if (!p)
578                 return;
579
580         while ((i = p->default_items)) {
581                 LIST_REMOVE(items, p->default_items, i);
582                 policy_item_free(i);
583         }
584
585         while ((i = p->mandatory_items)) {
586                 LIST_REMOVE(items, p->mandatory_items, i);
587                 policy_item_free(i);
588         }
589
590         while ((first = hashmap_steal_first(p->user_items))) {
591
592                 while ((i = first)) {
593                         LIST_REMOVE(items, first, i);
594                         policy_item_free(i);
595                 }
596         }
597
598         while ((first = hashmap_steal_first(p->group_items))) {
599
600                 while ((i = first)) {
601                         LIST_REMOVE(items, first, i);
602                         policy_item_free(i);
603                 }
604         }
605
606         hashmap_free(p->user_items);
607         hashmap_free(p->group_items);
608
609         p->user_items = p->group_items = NULL;
610 }
611
612 static void dump_items(PolicyItem *i, const char *prefix) {
613
614         if (!i)
615                 return;
616
617         if (!prefix)
618                 prefix = "";
619
620         printf("%sType: %s\n"
621                "%sClass: %s\n",
622                prefix, policy_item_type_to_string(i->type),
623                prefix, policy_item_class_to_string(i->class));
624
625         if (i->interface)
626                 printf("%sInterface: %s\n",
627                        prefix, i->interface);
628
629         if (i->member)
630                 printf("%sMember: %s\n",
631                        prefix, i->member);
632
633         if (i->error)
634                 printf("%sError: %s\n",
635                        prefix, i->error);
636
637         if (i->path)
638                 printf("%sPath: %s\n",
639                        prefix, i->path);
640
641         if (i->name)
642                 printf("%sName: %s\n",
643                        prefix, i->name);
644
645         if (i->message_type != 0)
646                 printf("%sMessage Type: %s\n",
647                        prefix, bus_message_type_to_string(i->message_type));
648
649         if (i->uid_valid) {
650                 _cleanup_free_ char *user;
651
652                 user = uid_to_name(i->uid);
653
654                 printf("%sUser: %s\n",
655                        prefix, strna(user));
656         }
657
658         if (i->gid_valid) {
659                 _cleanup_free_ char *group;
660
661                 group = gid_to_name(i->gid);
662
663                 printf("%sGroup: %s\n",
664                        prefix, strna(group));
665         }
666
667         if (i->items_next) {
668                 printf("%s%s\n", prefix, draw_special_char(DRAW_DASH));
669                 dump_items(i->items_next, prefix);
670         }
671 }
672
673 static void dump_hashmap_items(Hashmap *h) {
674         PolicyItem *i;
675         Iterator j;
676         void *k;
677
678         HASHMAP_FOREACH_KEY(i, k, h, j) {
679                 printf("\t%s Item for %u:\n", draw_special_char(DRAW_ARROW), PTR_TO_UINT(k));
680                 dump_items(i, "\t\t");
681         }
682 }
683
684 noreturn void policy_dump(Policy *p) {
685
686         printf("%s Default Items:\n", draw_special_char(DRAW_ARROW));
687         dump_items(p->default_items, "\t");
688
689         printf("%s Group Items:\n", draw_special_char(DRAW_ARROW));
690         dump_hashmap_items(p->group_items);
691
692         printf("%s User Items:\n", draw_special_char(DRAW_ARROW));
693         dump_hashmap_items(p->user_items);
694
695         printf("%s Mandatory Items:\n", draw_special_char(DRAW_ARROW));
696         dump_items(p->mandatory_items, "\t");
697
698         exit(0);
699 }
700
701 static const char* const policy_item_type_table[_POLICY_ITEM_TYPE_MAX] = {
702         [_POLICY_ITEM_TYPE_UNSET] = "unset",
703         [POLICY_ITEM_ALLOW] = "allow",
704         [POLICY_ITEM_DENY] = "deny",
705 };
706 DEFINE_STRING_TABLE_LOOKUP(policy_item_type, PolicyItemType);
707
708 static const char* const policy_item_class_table[_POLICY_ITEM_CLASS_MAX] = {
709         [_POLICY_ITEM_CLASS_UNSET] = "unset",
710         [POLICY_ITEM_SEND] = "send",
711         [POLICY_ITEM_RECV] = "recv",
712         [POLICY_ITEM_OWN] = "own",
713         [POLICY_ITEM_OWN_PREFIX] = "own-prefix",
714         [POLICY_ITEM_USER] = "user",
715         [POLICY_ITEM_GROUP] = "group",
716 };
717 DEFINE_STRING_TABLE_LOOKUP(policy_item_class, PolicyItemClass);