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