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