Updated connman to version 1.35
[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                 cleanup_config(policy);
276                 goto err;
277         }
278
279         pwd = getpwuid((uid_t)uid);
280         if (!pwd) {
281                 if (errno != 0)
282                         err = -errno;
283                 else
284                         err = -EINVAL;
285                 goto err;
286         }
287
288         policy->uid = g_strdup(pwd->pw_name);
289
290         nrgroups = 0;
291         getgrouplist(pwd->pw_name, pwd->pw_gid, NULL, &nrgroups);
292         groups = g_try_new0(gid_t, nrgroups);
293         if (!groups) {
294                 err = -ENOMEM;
295                 goto err;
296         }
297
298         err = getgrouplist(pwd->pw_name, pwd->pw_gid, groups, &nrgroups);
299         if (err < 0)
300                 goto err;
301
302         for (i = 0; i < nrgroups; i++) {
303                 grp = getgrgid(groups[i]);
304                 if (!grp) {
305                         if (errno != 0)
306                                 err = -errno;
307                         else
308                                 err = -EINVAL;
309                         goto err;
310                 }
311
312                 policy->gids = g_slist_prepend(policy->gids,
313                                         g_strdup(grp->gr_name));
314         }
315         g_free(groups);
316
317         owner = connman_session_get_owner(policy->session);
318
319         err = connman_dbus_get_selinux_context(connection, owner,
320                                                 selinux_context_reply, cbd);
321         if (err == 0) {
322                 /*
323                  * We are able to ask for a SELinux context. Let's defer the
324                  * creation of the session config until we get the answer
325                  * from D-Bus.
326                  */
327                 return;
328         }
329
330         finish_create(policy, cb, cbd->user_data);
331         g_free(cbd);
332
333         return;
334
335 err:
336         failed_create(NULL, cb, cbd->user_data, err);
337         g_free(cbd);
338         g_free(groups);
339 }
340
341 static int policy_local_create(struct connman_session *session,
342                                 connman_session_config_func_t cb,
343                                 void *user_data)
344 {
345         struct cb_data *cbd = cb_data_new(cb, user_data);
346         struct policy_config *policy;
347         const char *owner;
348         int err;
349
350         DBG("session %p", session);
351
352         policy = g_new0(struct policy_config, 1);
353         policy->config = connman_session_create_default_config();
354         policy->session = session;
355
356         cbd->data = policy;
357
358         owner = connman_session_get_owner(session);
359
360         err = connman_dbus_get_connection_unix_user(connection, owner,
361                                                 get_uid_reply, cbd);
362         if (err < 0) {
363                 connman_error("Could not get UID");
364                 cleanup_config(policy);
365                 g_free(cbd);
366                 return err;
367         }
368
369         return 0;
370 }
371
372 static void policy_local_destroy(struct connman_session *session)
373 {
374         struct policy_data *policy;
375
376         DBG("session %p", session);
377
378         policy = g_hash_table_lookup(session_hash, session);
379         if (!policy)
380                 return;
381
382         g_hash_table_remove(session_hash, session);
383 }
384
385 static struct connman_session_policy session_policy_local = {
386         .name = "session local policy configuration",
387         .priority = CONNMAN_SESSION_POLICY_PRIORITY_DEFAULT,
388         .create = policy_local_create,
389         .destroy = policy_local_destroy,
390 };
391
392 static int load_keyfile(const char *pathname, GKeyFile **keyfile)
393 {
394         GError *error = NULL;
395         int err;
396
397         *keyfile = g_key_file_new();
398
399         if (!g_key_file_load_from_file(*keyfile, pathname, 0, &error))
400                 goto err;
401
402         return 0;
403
404 err:
405         /*
406          * The fancy G_FILE_ERROR_* codes are identical to the native
407          * error codes.
408          */
409         err = -error->code;
410
411         DBG("Unable to load %s: %s", pathname, error->message);
412         g_clear_error(&error);
413
414         g_key_file_free(*keyfile);
415         *keyfile = NULL;
416
417         return err;
418 }
419
420 static int load_policy(GKeyFile *keyfile, const char *groupname,
421                         struct policy_group *group)
422 {
423         struct connman_session_config *config = group->config;
424         char *str, **tokens;
425         int i, err = 0;
426
427         group->selinux = g_key_file_get_string(keyfile, groupname,
428                                                 "selinux", NULL);
429
430         group->gid = g_key_file_get_string(keyfile, groupname,
431                                                 "gid", NULL);
432
433         group->uid = g_key_file_get_string(keyfile, groupname,
434                                                 "uid", NULL);
435
436         if (!group->selinux && !group->gid && !group->uid)
437                 return -EINVAL;
438
439         config->priority = g_key_file_get_boolean(keyfile, groupname,
440                                                 "Priority", NULL);
441
442         str = g_key_file_get_string(keyfile, groupname, "RoamingPolicy",
443                                 NULL);
444         if (str) {
445                 config->roaming_policy = connman_session_parse_roaming_policy(str);
446                 g_free(str);
447         }
448
449         str = g_key_file_get_string(keyfile, groupname, "ConnectionType",
450                                 NULL);
451         if (str) {
452                 config->type = connman_session_parse_connection_type(str);
453                 g_free(str);
454         }
455
456         config->ecall = g_key_file_get_boolean(keyfile, groupname,
457                                                 "EmergencyCall", NULL);
458
459         str = g_key_file_get_string(keyfile, groupname, "AllowedBearers",
460                                 NULL);
461         if (str) {
462                 g_slist_free(config->allowed_bearers);
463                 config->allowed_bearers = NULL;
464                 tokens = g_strsplit(str, " ", 0);
465
466                 for (i = 0; tokens[i]; i++) {
467                         err = connman_session_parse_bearers(tokens[i],
468                                         &config->allowed_bearers);
469                         if (err < 0)
470                                 break;
471                 }
472
473                 g_free(str);
474                 g_strfreev(tokens);
475         }
476
477         DBG("group %p selinux %s uid %s gid %s", group, group->selinux,
478                 group->uid, group->gid);
479
480         return err;
481 }
482
483 static void update_session(struct policy_config *policy)
484 {
485         DBG("policy %p session %p", policy, policy->session);
486
487         if (!policy->session)
488                 return;
489
490         if (connman_session_config_update(policy->session) < 0)
491                 connman_session_destroy(policy->session);
492 }
493
494 static void set_default_config(gpointer user_data)
495 {
496         struct policy_config *policy = user_data;
497
498         connman_session_set_default_config(policy->config);
499         policy->group = NULL;
500         update_session(policy);
501 }
502
503 static void cleanup_config(gpointer user_data)
504 {
505         struct policy_config *policy = user_data;
506
507         DBG("policy %p group %p", policy, policy->group);
508
509         if (policy->group)
510                 policy->group->sessions =
511                         g_slist_remove(policy->group->sessions, policy);
512
513         g_slist_free(policy->config->allowed_bearers);
514         g_free(policy->config->id);
515         g_free(policy->config);
516         g_free(policy->selinux_context);
517         g_free(policy->selinux);
518         g_free(policy->uid);
519         g_slist_free_full(policy->gids, g_free);
520         g_free(policy);
521 }
522
523 static void cleanup_group(gpointer user_data)
524 {
525         struct policy_group *group = user_data;
526
527         DBG("group %p", group);
528
529         g_slist_free_full(group->sessions, set_default_config);
530
531         g_slist_free(group->config->allowed_bearers);
532         g_free(group->config->id);
533         g_free(group->config);
534         if (group->selinux)
535                 g_hash_table_remove(selinux_hash, group->selinux);
536         if (group->uid)
537                 g_hash_table_remove(uid_hash, group->uid);
538         if (group->gid)
539                 g_hash_table_remove(gid_hash, group->gid);
540         g_free(group->selinux);
541         g_free(group->uid);
542         g_free(group->gid);
543         g_free(group);
544 }
545
546 static void cleanup_file(gpointer user_data)
547 {
548         struct policy_file *file = user_data;
549
550         DBG("file %p", file);
551
552         g_slist_free_full(file->groups, cleanup_group);
553         g_free(file);
554 }
555
556 static void recheck_sessions(void)
557 {
558         GHashTableIter iter;
559         gpointer value, key;
560         struct policy_group *group = NULL;
561         GSList *list;
562
563         g_hash_table_iter_init(&iter, session_hash);
564         while (g_hash_table_iter_next(&iter, &key, &value)) {
565                 struct policy_config *policy = value;
566
567                 if (policy->group)
568                         continue;
569
570                 if (policy->selinux)
571                         group = g_hash_table_lookup(selinux_hash,
572                                                         policy->selinux);
573                 if (group) {
574                         policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM;
575                         g_free(policy->config->id);
576                         policy->config->id = g_strdup(policy->selinux_context);
577                         update_session(policy);
578                         continue;
579                 }
580
581                 group = g_hash_table_lookup(uid_hash, policy->uid);
582                 if (group) {
583                         set_policy(policy, group);
584
585                         policy->config->id_type = CONNMAN_SESSION_ID_TYPE_UID;
586                         g_free(policy->config->id);
587                         policy->config->id = g_strdup(policy->uid);
588                         update_session(policy);
589                         continue;
590                 }
591
592                 for (list = policy->gids; list; list = list->next) {
593                         char *gid = list->data;
594                         group = g_hash_table_lookup(gid_hash, gid);
595                         if (group) {
596                                 set_policy(policy, group);
597
598                                 policy->config->id_type = CONNMAN_SESSION_ID_TYPE_GID;
599                                 g_free(policy->config->id);
600                                 policy->config->id = g_strdup(gid);
601                                 update_session(policy);
602                         }
603                 }
604         }
605 }
606
607 static int load_file(const char *filename, struct policy_file *file)
608 {
609         GKeyFile *keyfile;
610         struct policy_group *group;
611         char **groupnames;
612         char *pathname;
613         int err = 0, i;
614
615         DBG("%s", filename);
616
617         pathname = g_strdup_printf("%s/%s", POLICYDIR, filename);
618         err = load_keyfile(pathname, &keyfile);
619         g_free(pathname);
620
621         if (err < 0)
622                 return err;
623
624         groupnames = g_key_file_get_groups(keyfile, NULL);
625
626         for (i = 0; groupnames[i]; i++) {
627                 group = g_new0(struct policy_group, 1);
628                 group->config = connman_session_create_default_config();
629
630                 err = load_policy(keyfile, groupnames[i], group);
631                 if (err < 0) {
632                         g_free(group->config);
633                         g_free(group);
634                         break;
635                 }
636                 if (group->selinux)
637                         g_hash_table_replace(selinux_hash, group->selinux, group);
638
639                 if (group->uid)
640                         g_hash_table_replace(uid_hash, group->uid, group);
641
642                 if (group->gid)
643                         g_hash_table_replace(gid_hash, group->gid, group);
644
645                 file->groups = g_slist_prepend(file->groups, group);
646         }
647
648         g_strfreev(groupnames);
649
650         if (err < 0)
651                 g_slist_free_full(file->groups, cleanup_group);
652
653         g_key_file_free(keyfile);
654
655         return err;
656 }
657
658 static bool is_filename_valid(const char *filename)
659 {
660         if (!filename)
661                 return false;
662
663         if (filename[0] == '.')
664                 return false;
665
666         return g_str_has_suffix(filename, ".policy");
667 }
668
669 static int read_policies(void)
670 {
671         GDir *dir;
672         const gchar *filename;
673         struct policy_file *file;
674
675         DBG("");
676
677         dir = g_dir_open(POLICYDIR, 0, NULL);
678         if (!dir)
679                 return -EINVAL;
680
681         while ((filename = g_dir_read_name(dir))) {
682                 if (!is_filename_valid(filename))
683                         continue;
684
685                 file = g_new0(struct policy_file, 1);
686                 if (load_file(filename, file) < 0) {
687                         g_free(file);
688                         continue;
689                 }
690
691                 g_hash_table_replace(file_hash, g_strdup(filename), file);
692         }
693
694         g_dir_close(dir);
695
696         return 0;
697 }
698
699
700 static void notify_handler(struct inotify_event *event,
701                                         const char *filename)
702 {
703         struct policy_file *file;
704
705         DBG("event %x file %s", event->mask, filename);
706
707         if (event->mask & IN_CREATE)
708                 return;
709
710         if (!is_filename_valid(filename))
711                 return;
712
713         /*
714          * load_file() will modify the global selinux/uid/gid hash
715          * tables. We need to remove the old entries first before
716          * else the table points to the wrong entries.
717          */
718         g_hash_table_remove(file_hash, filename);
719
720         if (event->mask & (IN_DELETE | IN_MOVED_FROM))
721                 return;
722
723         if (event->mask & (IN_MOVED_TO | IN_MODIFY)) {
724                 connman_info("Policy update for '%s'", filename);
725
726                 file = g_new0(struct policy_file, 1);
727                 if (load_file(filename, file) < 0) {
728                         g_free(file);
729                         return;
730                 }
731
732                 g_hash_table_replace(file_hash, g_strdup(filename), file);
733                 recheck_sessions();
734         }
735 }
736
737 static int session_policy_local_init(void)
738 {
739         int err;
740
741         DBG("");
742
743         /* If the dir doesn't exist, create it */
744         if (!g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR)) {
745                 if (mkdir(POLICYDIR, MODE) < 0) {
746                         if (errno != EEXIST)
747                                 return -errno;
748                 }
749         }
750
751         connection = connman_dbus_get_connection();
752         if (!connection)
753                 return -EIO;
754
755         file_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
756                                         g_free, cleanup_file);
757         session_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
758                                                 NULL, cleanup_config);
759         selinux_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
760                                         NULL, NULL);
761         uid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
762                                         NULL, NULL);
763         gid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
764                                         NULL, NULL);
765
766         err = connman_inotify_register(POLICYDIR, notify_handler);
767         if (err < 0)
768                 goto err;
769
770         err = connman_session_policy_register(&session_policy_local);
771         if (err < 0)
772                 goto err_notify;
773
774         read_policies();
775
776         return 0;
777
778 err_notify:
779
780         connman_inotify_unregister(POLICYDIR, notify_handler);
781
782 err:
783         if (file_hash)
784                 g_hash_table_destroy(file_hash);
785
786         if (session_hash)
787                 g_hash_table_destroy(session_hash);
788
789         if (selinux_hash)
790                 g_hash_table_destroy(selinux_hash);
791
792         if (uid_hash)
793                 g_hash_table_destroy(uid_hash);
794
795         if (gid_hash)
796                 g_hash_table_destroy(gid_hash);
797
798         connman_session_policy_unregister(&session_policy_local);
799
800         dbus_connection_unref(connection);
801
802         return err;
803 }
804
805 static void session_policy_local_exit(void)
806 {
807         DBG("");
808
809         g_hash_table_destroy(file_hash);
810         g_hash_table_destroy(session_hash);
811         g_hash_table_destroy(selinux_hash);
812         g_hash_table_destroy(uid_hash);
813         g_hash_table_destroy(gid_hash);
814
815         connman_session_policy_unregister(&session_policy_local);
816
817         dbus_connection_unref(connection);
818
819         connman_inotify_unregister(POLICYDIR, notify_handler);
820 }
821
822 CONNMAN_PLUGIN_DEFINE(session_policy_local,
823                 "Session local file policy configuration plugin",
824                 VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
825                 session_policy_local_init, session_policy_local_exit)