9beb0980e62bbf076747765a2f4900fabe2dc7fd
[platform/upstream/connman.git] / plugins / session_policy_local.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2012-2014  BMW Car IT GmbH.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
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
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <string.h>
28 #include <sys/inotify.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <pwd.h>
32 #include <grp.h>
33
34 #include <glib.h>
35
36 #include <gdbus.h>
37
38 #define CONNMAN_API_SUBJECT_TO_CHANGE
39 #include <connman/plugin.h>
40 #include <connman/log.h>
41 #include <connman/session.h>
42 #include <connman/dbus.h>
43 #include <connman/inotify.h>
44
45 #include "src/shared/util.h"
46
47 #define POLICYDIR STORAGEDIR "/session_policy_local"
48
49 #define MODE            (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | \
50                         S_IXGRP | S_IROTH | S_IXOTH)
51
52 static DBusConnection *connection;
53
54 static GHashTable *file_hash;    /* (filename, policy_file) */
55 static GHashTable *session_hash; /* (connman_session, policy_config) */
56
57 /* Global lookup tables for mapping sessions to policies */
58 static GHashTable *selinux_hash; /* (lsm context, policy_group) */
59 static GHashTable *uid_hash;     /* (uid, policy_group) */
60 static GHashTable *gid_hash;     /* (gid, policy_group) */
61
62 /*
63  * A instance of struct policy_file is created per file in
64  * POLICYDIR.
65  */
66 struct policy_file {
67         /*
68          * A valid file is a keyfile with one ore more groups. All
69          * groups are keept in this list.
70          */
71         GSList *groups;
72 };
73
74 struct policy_group {
75         char *selinux;
76         char *uid;
77         char *gid;
78
79         /*
80          * Each policy_group owns a config and is not shared with
81          * sessions. Instead each session copies the valued from this
82          * object.
83          */
84         struct connman_session_config *config;
85
86         /* All 'users' of this policy. */
87         GSList *sessions;
88 };
89
90 /* A struct policy_config object is created and owned by a session. */
91 struct policy_config {
92         char *selinux;
93         char *selinux_context;
94         char *uid;
95         GSList *gids;
96
97         /* The policy config owned by the session */
98         struct connman_session_config *config;
99
100         /* To which policy belongs this policy_config */
101         struct connman_session *session;
102         /*
103          * Points to the policy_group when a config has been applied
104          * from a file.
105          */
106         struct policy_group *group;
107 };
108
109 static void copy_session_config(struct connman_session_config *dst,
110                         struct connman_session_config *src)
111 {
112         g_slist_free(dst->allowed_bearers);
113         dst->allowed_bearers = g_slist_copy(src->allowed_bearers);
114         dst->ecall = src->ecall;
115         dst->type = src->type;
116         dst->roaming_policy = src->roaming_policy;
117         dst->priority = src->priority;
118 }
119
120 static void set_policy(struct policy_config *policy,
121                         struct policy_group *group)
122 {
123         DBG("policy %p group %p", policy, group);
124
125         group->sessions = g_slist_prepend(group->sessions, policy);
126         policy->group = group;
127
128         copy_session_config(policy->config, group->config);
129 }
130
131 static char *parse_selinux_type(const char *context)
132 {
133         char *ident, **tokens;
134
135         /*
136          * SELinux combines Role-Based Access Control (RBAC), Type
137          * Enforcment (TE) and optionally Multi-Level Security (MLS).
138          *
139          * When SELinux is enabled all processes and files are labeled
140          * with a contex that contains information such as user, role
141          * type (and optionally a level). E.g.
142          *
143          * $ ls -Z
144          * -rwxrwxr-x. wagi wagi unconfined_u:object_r:haifux_exec_t:s0 session_ui.py
145          *
146          * For identifyng application we (ab)using the type
147          * information. In the above example the haifux_exec_t type
148          * will be transfered to haifux_t as defined in the domain
149          * transition and thus we are able to identify the application
150          * as haifux_t.
151          */
152
153         tokens = g_strsplit(context, ":", 0);
154         if (g_strv_length(tokens) < 2) {
155                 g_strfreev(tokens);
156                 return NULL;
157         }
158
159         /* Use the SELinux type as identification token. */
160         ident = g_strdup(tokens[2]);
161
162         g_strfreev(tokens);
163
164         return ident;
165 }
166
167 static void cleanup_config(gpointer user_data);
168
169 static void finish_create(struct policy_config *policy,
170                                 connman_session_config_func_t cb,
171                                 void *user_data)
172 {
173         struct policy_group *group = NULL;
174         GSList *list;
175
176         if (policy->selinux)
177                 group = g_hash_table_lookup(selinux_hash, policy->selinux);
178
179         if (group) {
180                 set_policy(policy, group);
181
182                 policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM;
183                 policy->config->id = g_strdup(policy->selinux_context);
184                 goto done;
185         }
186
187         if (policy->uid)
188                 group = g_hash_table_lookup(uid_hash, policy->uid);
189
190         if (group) {
191                 set_policy(policy, group);
192
193                 policy->config->id_type = CONNMAN_SESSION_ID_TYPE_UID;
194                 policy->config->id = g_strdup(policy->uid);
195                 goto done;
196         }
197
198         for (list = policy->gids; list; list = list->next) {
199                 char *gid = list->data;
200
201                 group = g_hash_table_lookup(gid_hash, gid);
202                 if (!group)
203                         continue;
204
205                 set_policy(policy, group);
206
207                 policy->config->id_type = CONNMAN_SESSION_ID_TYPE_GID;
208                 policy->config->id = g_strdup(gid);
209                 break;
210         }
211 done:
212         g_hash_table_replace(session_hash, policy->session, policy);
213
214         (*cb)(policy->session, policy->config, user_data, 0);
215 }
216
217 static void failed_create(struct policy_config *policy,
218                         connman_session_config_func_t cb,
219                         void *user_data, int err)
220 {
221         (*cb)(policy->session, NULL, user_data, err);
222
223         cleanup_config(policy);
224 }
225
226 static void selinux_context_reply(const unsigned char *context, void *user_data,
227                                         int err)
228 {
229         struct cb_data *cbd = user_data;
230         connman_session_config_func_t cb = cbd->cb;
231         struct policy_config *policy = cbd->data;
232         char *ident = NULL;
233
234         DBG("session %p", policy->session);
235
236         if (err == -EIO) {
237                 /* No SELinux support, drop back to UID/GID only mode */
238                 finish_create(policy, cb, cbd->user_data);
239                 goto done;
240         }
241
242         if (err < 0) {
243                 failed_create(policy, cb, cbd->user_data, err);
244                 goto done;
245         }
246
247         DBG("SELinux context %s", context);
248
249         policy->selinux_context = g_strdup((const char *)context);
250         ident = parse_selinux_type(policy->selinux_context);
251         if (ident)
252                 policy->selinux = g_strdup(ident);
253
254         finish_create(policy, cb, cbd->user_data);
255
256 done:
257         g_free(cbd);
258         g_free(ident);
259 }
260
261 static void get_uid_reply(unsigned int uid, void *user_data, int err)
262 {
263         struct cb_data *cbd = user_data;
264         connman_session_config_func_t cb = cbd->cb;
265         struct policy_config *policy = cbd->data;
266         const char *owner;
267         struct passwd *pwd;
268         struct group *grp;
269         gid_t *groups = NULL;
270         int nrgroups, i;
271
272         DBG("session %p uid %d", policy->session, uid);
273
274         if (err < 0)
275                 goto err;
276
277         pwd = getpwuid((uid_t)uid);
278         if (!pwd) {
279                 if (errno != 0)
280                         err = -errno;
281                 else
282                         err = -EINVAL;
283                 goto err;
284         }
285
286         policy->uid = g_strdup(pwd->pw_name);
287
288         nrgroups = 0;
289         getgrouplist(pwd->pw_name, pwd->pw_gid, NULL, &nrgroups);
290         groups = g_try_new0(gid_t, nrgroups);
291         if (!groups) {
292                 err = -ENOMEM;
293                 goto err;
294         }
295
296         err = getgrouplist(pwd->pw_name, pwd->pw_gid, groups, &nrgroups);
297         if (err < 0)
298                 goto err;
299
300         for (i = 0; i < nrgroups; i++) {
301                 grp = getgrgid(groups[i]);
302                 if (!grp) {
303                         if (errno != 0)
304                                 err = -errno;
305                         else
306                                 err = -EINVAL;
307                         goto err;
308                 }
309
310                 policy->gids = g_slist_prepend(policy->gids,
311                                         g_strdup(grp->gr_name));
312         }
313         g_free(groups);
314
315         owner = connman_session_get_owner(policy->session);
316
317         err = connman_dbus_get_selinux_context(connection, owner,
318                                                 selinux_context_reply, cbd);
319         if (err == 0) {
320                 /*
321                  * We are able to ask for a SELinux context. Let's defer the
322                  * creation of the session config until we get the answer
323                  * from D-Bus.
324                  */
325                 return;
326         }
327
328         finish_create(policy, cb, cbd->user_data);
329         g_free(cbd);
330
331         return;
332
333 err:
334         failed_create(policy, cb, cbd->user_data, err);
335         g_free(cbd);
336         g_free(groups);
337 }
338
339 static int policy_local_create(struct connman_session *session,
340                                 connman_session_config_func_t cb,
341                                 void *user_data)
342 {
343         struct cb_data *cbd = cb_data_new(cb, user_data);
344         struct policy_config *policy;
345         const char *owner;
346         int err;
347
348         DBG("session %p", session);
349
350         policy = g_new0(struct policy_config, 1);
351         policy->config = connman_session_create_default_config();
352         policy->session = session;
353
354         cbd->data = policy;
355
356         owner = connman_session_get_owner(session);
357
358         err = connman_dbus_get_connection_unix_user(connection, owner,
359                                                 get_uid_reply, cbd);
360         if (err < 0) {
361                 connman_error("Could not get UID");
362                 cleanup_config(policy);
363                 g_free(cbd);
364                 return err;
365         }
366
367         return 0;
368 }
369
370 static void policy_local_destroy(struct connman_session *session)
371 {
372         struct policy_data *policy;
373
374         DBG("session %p", session);
375
376         policy = g_hash_table_lookup(session_hash, session);
377         if (!policy)
378                 return;
379
380         g_hash_table_remove(session_hash, session);
381 }
382
383 static struct connman_session_policy session_policy_local = {
384         .name = "session local policy configuration",
385         .priority = CONNMAN_SESSION_POLICY_PRIORITY_DEFAULT,
386         .create = policy_local_create,
387         .destroy = policy_local_destroy,
388 };
389
390 static int load_keyfile(const char *pathname, GKeyFile **keyfile)
391 {
392         GError *error = NULL;
393         int err;
394
395         *keyfile = g_key_file_new();
396
397         if (!g_key_file_load_from_file(*keyfile, pathname, 0, &error))
398                 goto err;
399
400         return 0;
401
402 err:
403         /*
404          * The fancy G_FILE_ERROR_* codes are identical to the native
405          * error codes.
406          */
407         err = -error->code;
408
409         DBG("Unable to load %s: %s", pathname, error->message);
410         g_clear_error(&error);
411
412         g_key_file_free(*keyfile);
413         *keyfile = NULL;
414
415         return err;
416 }
417
418 static int load_policy(GKeyFile *keyfile, const char *groupname,
419                         struct policy_group *group)
420 {
421         struct connman_session_config *config = group->config;
422         char *str, **tokens;
423         int i, err = 0;
424
425         group->selinux = g_key_file_get_string(keyfile, groupname,
426                                                 "selinux", NULL);
427
428         group->gid = g_key_file_get_string(keyfile, groupname,
429                                                 "gid", NULL);
430
431         group->uid = g_key_file_get_string(keyfile, groupname,
432                                                 "uid", NULL);
433
434         if (!group->selinux && !group->gid && !group->uid)
435                 return -EINVAL;
436
437         config->priority = g_key_file_get_boolean(keyfile, groupname,
438                                                 "Priority", NULL);
439
440         str = g_key_file_get_string(keyfile, groupname, "RoamingPolicy",
441                                 NULL);
442         if (str) {
443                 config->roaming_policy = connman_session_parse_roaming_policy(str);
444                 g_free(str);
445         }
446
447         str = g_key_file_get_string(keyfile, groupname, "ConnectionType",
448                                 NULL);
449         if (str) {
450                 config->type = connman_session_parse_connection_type(str);
451                 g_free(str);
452         }
453
454         config->ecall = g_key_file_get_boolean(keyfile, groupname,
455                                                 "EmergencyCall", NULL);
456
457         str = g_key_file_get_string(keyfile, groupname, "AllowedBearers",
458                                 NULL);
459         if (str) {
460                 g_slist_free(config->allowed_bearers);
461                 config->allowed_bearers = NULL;
462                 tokens = g_strsplit(str, " ", 0);
463
464                 for (i = 0; tokens[i]; i++) {
465                         err = connman_session_parse_bearers(tokens[i],
466                                         &config->allowed_bearers);
467                         if (err < 0)
468                                 break;
469                 }
470
471                 g_free(str);
472                 g_strfreev(tokens);
473         }
474
475         DBG("group %p selinux %s uid %s gid %s", group, group->selinux,
476                 group->uid, group->gid);
477
478         return err;
479 }
480
481 static void update_session(struct policy_config *policy)
482 {
483         DBG("policy %p session %p", policy, policy->session);
484
485         if (!policy->session)
486                 return;
487
488         if (connman_session_config_update(policy->session) < 0)
489                 connman_session_destroy(policy->session);
490 }
491
492 static void set_default_config(gpointer user_data)
493 {
494         struct policy_config *policy = user_data;
495
496         connman_session_set_default_config(policy->config);
497         policy->group = NULL;
498         update_session(policy);
499 }
500
501 static void cleanup_config(gpointer user_data)
502 {
503         struct policy_config *policy = user_data;
504
505         DBG("policy %p group %p", policy, policy->group);
506
507         if (policy->group)
508                 policy->group->sessions =
509                         g_slist_remove(policy->group->sessions, policy);
510
511         g_slist_free(policy->config->allowed_bearers);
512         g_free(policy->config->id);
513         g_free(policy->config);
514         g_free(policy->selinux_context);
515         g_free(policy->selinux);
516         g_free(policy->uid);
517         g_slist_free_full(policy->gids, g_free);
518         g_free(policy);
519 }
520
521 static void cleanup_group(gpointer user_data)
522 {
523         struct policy_group *group = user_data;
524
525         DBG("group %p", group);
526
527         g_slist_free_full(group->sessions, set_default_config);
528
529         g_slist_free(group->config->allowed_bearers);
530         g_free(group->config->id);
531         g_free(group->config);
532         if (group->selinux)
533                 g_hash_table_remove(selinux_hash, group->selinux);
534         if (group->uid)
535                 g_hash_table_remove(uid_hash, group->uid);
536         if (group->gid)
537                 g_hash_table_remove(gid_hash, group->gid);
538         g_free(group->selinux);
539         g_free(group->uid);
540         g_free(group->gid);
541         g_free(group);
542 }
543
544 static void cleanup_file(gpointer user_data)
545 {
546         struct policy_file *file = user_data;
547
548         DBG("file %p", file);
549
550         g_slist_free_full(file->groups, cleanup_group);
551         g_free(file);
552 }
553
554 static void recheck_sessions(void)
555 {
556         GHashTableIter iter;
557         gpointer value, key;
558         struct policy_group *group = NULL;
559         GSList *list;
560
561         g_hash_table_iter_init(&iter, session_hash);
562         while (g_hash_table_iter_next(&iter, &key, &value)) {
563                 struct policy_config *policy = value;
564
565                 if (policy->group)
566                         continue;
567
568                 if (policy->selinux)
569                         group = g_hash_table_lookup(selinux_hash,
570                                                         policy->selinux);
571                 if (group) {
572                         policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM;
573                         g_free(policy->config->id);
574                         policy->config->id = g_strdup(policy->selinux_context);
575                         update_session(policy);
576                         continue;
577                 }
578
579                 group = g_hash_table_lookup(uid_hash, policy->uid);
580                 if (group) {
581                         set_policy(policy, group);
582
583                         policy->config->id_type = CONNMAN_SESSION_ID_TYPE_UID;
584                         g_free(policy->config->id);
585                         policy->config->id = g_strdup(policy->uid);
586                         update_session(policy);
587                         continue;
588                 }
589
590                 for (list = policy->gids; list; list = list->next) {
591                         char *gid = list->data;
592                         group = g_hash_table_lookup(gid_hash, gid);
593                         if (group) {
594                                 set_policy(policy, group);
595
596                                 policy->config->id_type = CONNMAN_SESSION_ID_TYPE_GID;
597                                 g_free(policy->config->id);
598                                 policy->config->id = g_strdup(gid);
599                                 update_session(policy);
600                         }
601                 }
602         }
603 }
604
605 static int load_file(const char *filename, struct policy_file *file)
606 {
607         GKeyFile *keyfile;
608         struct policy_group *group;
609         char **groupnames;
610         char *pathname;
611         int err = 0, i;
612
613         DBG("%s", filename);
614
615         pathname = g_strdup_printf("%s/%s", POLICYDIR, filename);
616         err = load_keyfile(pathname, &keyfile);
617         g_free(pathname);
618
619         if (err < 0)
620                 return err;
621
622         groupnames = g_key_file_get_groups(keyfile, NULL);
623
624         for (i = 0; groupnames[i]; i++) {
625                 group = g_new0(struct policy_group, 1);
626                 group->config = connman_session_create_default_config();
627
628                 err = load_policy(keyfile, groupnames[i], group);
629                 if (err < 0) {
630                         g_free(group->config);
631                         g_free(group);
632                         break;
633                 }
634                 if (group->selinux)
635                         g_hash_table_replace(selinux_hash, group->selinux, group);
636
637                 if (group->uid)
638                         g_hash_table_replace(uid_hash, group->uid, group);
639
640                 if (group->gid)
641                         g_hash_table_replace(gid_hash, group->gid, group);
642
643                 file->groups = g_slist_prepend(file->groups, group);
644         }
645
646         g_strfreev(groupnames);
647
648         if (err < 0)
649                 g_slist_free_full(file->groups, cleanup_group);
650
651         g_key_file_free(keyfile);
652
653         return err;
654 }
655
656 static bool is_filename_valid(const char *filename)
657 {
658         if (!filename)
659                 return false;
660
661         if (filename[0] == '.')
662                 return false;
663
664         return g_str_has_suffix(filename, ".policy");
665 }
666
667 static int read_policies(void)
668 {
669         GDir *dir;
670         const gchar *filename;
671         struct policy_file *file;
672
673         DBG("");
674
675         dir = g_dir_open(POLICYDIR, 0, NULL);
676         if (!dir)
677                 return -EINVAL;
678
679         while ((filename = g_dir_read_name(dir))) {
680                 if (!is_filename_valid(filename))
681                         continue;
682
683                 file = g_new0(struct policy_file, 1);
684                 if (load_file(filename, file) < 0) {
685                         g_free(file);
686                         continue;
687                 }
688
689                 g_hash_table_replace(file_hash, g_strdup(filename), file);
690         }
691
692         g_dir_close(dir);
693
694         return 0;
695 }
696
697
698 static void notify_handler(struct inotify_event *event,
699                                         const char *filename)
700 {
701         struct policy_file *file;
702
703         DBG("event %x file %s", event->mask, filename);
704
705         if (event->mask & IN_CREATE)
706                 return;
707
708         if (!is_filename_valid(filename))
709                 return;
710
711         /*
712          * load_file() will modify the global selinux/uid/gid hash
713          * tables. We need to remove the old entries first before
714          * else the table points to the wrong entries.
715          */
716         g_hash_table_remove(file_hash, filename);
717
718         if (event->mask & (IN_DELETE | IN_MOVED_FROM))
719                 return;
720
721         if (event->mask & (IN_MOVED_TO | IN_MODIFY)) {
722                 connman_info("Policy update for '%s'", filename);
723
724                 file = g_new0(struct policy_file, 1);
725                 if (load_file(filename, file) < 0) {
726                         g_free(file);
727                         return;
728                 }
729
730                 g_hash_table_replace(file_hash, g_strdup(filename), file);
731                 recheck_sessions();
732         }
733 }
734
735 static int session_policy_local_init(void)
736 {
737         int err;
738
739         DBG("");
740
741         /* If the dir doesn't exist, create it */
742         if (!g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR)) {
743                 if (mkdir(POLICYDIR, MODE) < 0) {
744                         if (errno != EEXIST)
745                                 return -errno;
746                 }
747         }
748
749         connection = connman_dbus_get_connection();
750         if (!connection)
751                 return -EIO;
752
753         file_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
754                                         g_free, cleanup_file);
755         session_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
756                                                 NULL, cleanup_config);
757         selinux_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
758                                         NULL, NULL);
759         uid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
760                                         NULL, NULL);
761         gid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
762                                         NULL, NULL);
763
764         err = connman_inotify_register(POLICYDIR, notify_handler);
765         if (err < 0)
766                 goto err;
767
768         err = connman_session_policy_register(&session_policy_local);
769         if (err < 0)
770                 goto err_notify;
771
772         read_policies();
773
774         return 0;
775
776 err_notify:
777
778         connman_inotify_unregister(POLICYDIR, notify_handler);
779
780 err:
781         if (file_hash)
782                 g_hash_table_destroy(file_hash);
783
784         if (session_hash)
785                 g_hash_table_destroy(session_hash);
786
787         if (selinux_hash)
788                 g_hash_table_destroy(selinux_hash);
789
790         if (uid_hash)
791                 g_hash_table_destroy(uid_hash);
792
793         if (gid_hash)
794                 g_hash_table_destroy(gid_hash);
795
796         connman_session_policy_unregister(&session_policy_local);
797
798         dbus_connection_unref(connection);
799
800         return err;
801 }
802
803 static void session_policy_local_exit(void)
804 {
805         DBG("");
806
807         g_hash_table_destroy(file_hash);
808         g_hash_table_destroy(session_hash);
809         g_hash_table_destroy(selinux_hash);
810         g_hash_table_destroy(uid_hash);
811         g_hash_table_destroy(gid_hash);
812
813         connman_session_policy_unregister(&session_policy_local);
814
815         dbus_connection_unref(connection);
816
817         connman_inotify_unregister(POLICYDIR, notify_handler);
818 }
819
820 CONNMAN_PLUGIN_DEFINE(session_policy_local,
821                 "Session local file policy configuration plugin",
822                 VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
823                 session_policy_local_init, session_policy_local_exit)