DA: Skip initializing failed_bssids list when eapol failure case
[platform/upstream/connman.git] / plugins / session_policy_local.c
old mode 100644 (file)
new mode 100755 (executable)
index ecaf15c..32b9c69
@@ -2,7 +2,7 @@
  *
  *  Connection Manager
  *
- *  Copyright (C) 2012  BMW Car IT GbmH. All rights reserved.
+ *  Copyright (C) 2012-2014  BMW Car IT GmbH.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
@@ -27,6 +27,9 @@
 #include <string.h>
 #include <sys/inotify.h>
 #include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
 
 #include <glib.h>
 
@@ -39,6 +42,8 @@
 #include <connman/dbus.h>
 #include <connman/inotify.h>
 
+#include "src/shared/util.h"
+
 #define POLICYDIR STORAGEDIR "/session_policy_local"
 
 #define MODE           (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | \
 
 static DBusConnection *connection;
 
-static GHashTable *policy_hash;
-static GHashTable *session_hash;
+static GHashTable *file_hash;    /* (filename, policy_file) */
+static GHashTable *session_hash; /* (connman_session, policy_config) */
 
-struct create_data {
-       struct connman_session *session;
-       connman_session_config_cb callback;
-       void *user_data;
+/* Global lookup tables for mapping sessions to policies */
+static GHashTable *selinux_hash; /* (lsm context, policy_group) */
+static GHashTable *uid_hash;     /* (uid, policy_group) */
+static GHashTable *gid_hash;     /* (gid, policy_group) */
+
+/*
+ * A instance of struct policy_file is created per file in
+ * POLICYDIR.
+ */
+struct policy_file {
+       /*
+        * A valid file is a keyfile with one ore more groups. All
+        * groups are kept in this list.
+        */
+       GSList *groups;
 };
 
-struct policy_data {
-       int refcount;
-       char *ident;
+struct policy_group {
+       char *selinux;
+       char *uid;
+       char *gid;
 
-       struct connman_session *session;
+       /*
+        * Each policy_group owns a config and is not shared with
+        * sessions. Instead each session copies the valued from this
+        * object.
+        */
+       struct connman_session_config *config;
+
+       /* All 'users' of this policy. */
+       GSList *sessions;
+};
+
+/* A struct policy_config object is created and owned by a session. */
+struct policy_config {
+       char *selinux;
+       char *selinux_context;
+       char *uid;
+       GSList *gids;
+
+       /* The policy config owned by the session */
        struct connman_session_config *config;
+
+       /* To which policy belongs this policy_config */
+       struct connman_session *session;
+       /*
+        * Points to the policy_group when a config has been applied
+        * from a file.
+        */
+       struct policy_group *group;
 };
 
-static void cleanup_policy(gpointer user_data)
+static void copy_session_config(struct connman_session_config *dst,
+                       struct connman_session_config *src)
 {
-       struct policy_data *policy = user_data;
+       g_slist_free(dst->allowed_bearers);
+       dst->allowed_bearers = g_slist_copy(src->allowed_bearers);
+       dst->ecall = src->ecall;
+       dst->type = src->type;
+       dst->roaming_policy = src->roaming_policy;
+       dst->priority = src->priority;
+}
 
-       if (policy->config != NULL)
-               g_slist_free(policy->config->allowed_bearers);
+static void set_policy(struct policy_config *policy,
+                       struct policy_group *group)
+{
+       DBG("policy %p group %p", policy, group);
 
-       g_free(policy->ident);
-       g_free(policy->config);
-       g_free(policy);
+       group->sessions = g_slist_prepend(group->sessions, policy);
+       policy->group = group;
+
+       copy_session_config(policy->config, group->config);
 }
 
-static char *parse_ident(const unsigned char *context)
+static char *parse_selinux_type(const char *context)
 {
-       char *str, *ident, **tokens;
+       char *ident, **tokens;
 
        /*
         * SELinux combines Role-Based Access Control (RBAC), Type
-        * Enforcment (TE) and optionally Multi-Level Security (MLS).
+        * Enforcement (TE) and optionally Multi-Level Security (MLS).
         *
         * When SELinux is enabled all processes and files are labeled
         * with a contex that contains information such as user, role
@@ -92,20 +145,14 @@ static char *parse_ident(const unsigned char *context)
         *
         * For identifyng application we (ab)using the type
         * information. In the above example the haifux_exec_t type
-        * will be transfered to haifux_t as defined in the domain
+        * will be transferred to haifux_t as defined in the domain
         * transition and thus we are able to identify the application
         * as haifux_t.
         */
 
-       str = g_strdup((const gchar*)context);
-       if (str == NULL)
-               return NULL;
-
-       DBG("SELinux context %s", str);
-
-       tokens = g_strsplit(str, ":", 0);
-       if (tokens == NULL) {
-               g_free(str);
+       tokens = g_strsplit(context, ":", 0);
+       if (g_strv_length(tokens) < 2) {
+               g_strfreev(tokens);
                return NULL;
        }
 
@@ -113,121 +160,207 @@ static char *parse_ident(const unsigned char *context)
        ident = g_strdup(tokens[2]);
 
        g_strfreev(tokens);
-       g_free(str);
 
        return ident;
 }
 
-static struct policy_data *create_policy(const char *ident)
+static void cleanup_config(gpointer user_data);
+
+static void finish_create(struct policy_config *policy,
+                               connman_session_config_func_t cb,
+                               void *user_data)
 {
-       struct policy_data *policy;
+       struct policy_group *group = NULL;
+       GSList *list;
 
-       DBG("ident %s", ident);
+       if (policy->selinux)
+               group = g_hash_table_lookup(selinux_hash, policy->selinux);
 
-       policy = g_try_new0(struct policy_data, 1);
-       if (policy == NULL)
-               return NULL;
+       if (group) {
+               set_policy(policy, group);
 
-       policy->config = connman_session_create_default_config();
-       if (policy->config == NULL) {
-               g_free(policy);
-               return NULL;
+               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM;
+               policy->config->id = g_strdup(policy->selinux_context);
+               goto done;
        }
 
-       policy->refcount = 1;
-       policy->ident = g_strdup(ident);
+       if (policy->uid)
+               group = g_hash_table_lookup(uid_hash, policy->uid);
 
-       g_hash_table_replace(policy_hash, policy->ident, policy);
+       if (group) {
+               set_policy(policy, group);
 
-       return policy;
-}
+               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_UID;
+               policy->config->id = g_strdup(policy->uid);
+               goto done;
+       }
 
-static struct policy_data *policy_ref(struct policy_data *policy)
-{
-       DBG("%p %s ref %d", policy, policy->ident, policy->refcount + 1);
+       for (list = policy->gids; list; list = list->next) {
+               char *gid = list->data;
+
+               group = g_hash_table_lookup(gid_hash, gid);
+               if (!group)
+                       continue;
 
-       __sync_fetch_and_add(&policy->refcount, 1);
+               set_policy(policy, group);
 
-       return policy;
+               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_GID;
+               policy->config->id = g_strdup(gid);
+               break;
+       }
+done:
+       g_hash_table_replace(session_hash, policy->session, policy);
+
+       (*cb)(policy->session, policy->config, user_data, 0);
 }
 
-static void policy_unref(struct policy_data *policy)
+static void failed_create(struct policy_config *policy,
+                       connman_session_config_func_t cb,
+                       void *user_data, int err)
 {
-       DBG(" %p %s ref %d", policy, policy->ident, policy->refcount - 1);
-
-       if (__sync_fetch_and_sub(&policy->refcount, 1) != 1)
-               return;
+       (*cb)(policy->session, NULL, user_data, err);
 
-       g_hash_table_remove(policy_hash, policy->ident);
-};
+       cleanup_config(policy);
+}
 
 static void selinux_context_reply(const unsigned char *context, void *user_data,
                                        int err)
 {
-       struct create_data *data = user_data;
-       struct policy_data *policy;
-       struct connman_session_config *config = NULL;
+       struct cb_data *cbd = user_data;
+       connman_session_config_func_t cb = cbd->cb;
+       struct policy_config *policy = cbd->data;
        char *ident = NULL;
 
-       DBG("session %p", data->session);
+       DBG("session %p", policy->session);
 
-       if (err < 0)
+       if (err == -EIO) {
+               /* No SELinux support, drop back to UID/GID only mode */
+               finish_create(policy, cb, cbd->user_data);
                goto done;
+       }
 
-       ident = parse_ident(context);
-       if (ident == NULL) {
-               err = -EINVAL;
+       if (err < 0) {
+               failed_create(policy, cb, cbd->user_data, err);
                goto done;
        }
 
-       policy = g_hash_table_lookup(policy_hash, ident);
-       if (policy != NULL) {
-               policy_ref(policy);
-               policy->session = data->session;
-       } else {
-               policy = create_policy(ident);
-               if (policy == NULL) {
-                       err = -ENOMEM;
-                       goto done;
+       DBG("SELinux context %s", context);
+
+       policy->selinux_context = g_strdup((const char *)context);
+       ident = parse_selinux_type(policy->selinux_context);
+       if (ident)
+               policy->selinux = g_strdup(ident);
+
+       finish_create(policy, cb, cbd->user_data);
+
+done:
+       g_free(cbd);
+       g_free(ident);
+}
+
+static void get_uid_reply(unsigned int uid, void *user_data, int err)
+{
+       struct cb_data *cbd = user_data;
+       connman_session_config_func_t cb = cbd->cb;
+       struct policy_config *policy = cbd->data;
+       const char *owner;
+       struct passwd *pwd;
+       struct group *grp;
+       gid_t *groups = NULL;
+       int nrgroups, i;
+
+       DBG("session %p uid %d", policy->session, uid);
+
+       if (err < 0)
+               goto err;
+
+       pwd = getpwuid((uid_t)uid);
+       if (!pwd) {
+               if (errno != 0)
+                       err = -errno;
+               else
+                       err = -EINVAL;
+               goto err;
+       }
+
+       policy->uid = g_strdup(pwd->pw_name);
+
+       nrgroups = 0;
+       getgrouplist(pwd->pw_name, pwd->pw_gid, NULL, &nrgroups);
+       groups = g_try_new0(gid_t, nrgroups);
+       if (!groups) {
+               err = -ENOMEM;
+               goto err;
+       }
+
+       err = getgrouplist(pwd->pw_name, pwd->pw_gid, groups, &nrgroups);
+       if (err < 0)
+               goto err;
+
+       for (i = 0; i < nrgroups; i++) {
+               grp = getgrgid(groups[i]);
+               if (!grp) {
+                       if (errno != 0)
+                               err = -errno;
+                       else
+                               err = -EINVAL;
+                       goto err;
                }
+
+               policy->gids = g_slist_prepend(policy->gids,
+                                       g_strdup(grp->gr_name));
        }
+       g_free(groups);
 
-       g_hash_table_replace(session_hash, data->session, policy);
-       config = policy->config;
+       owner = connman_session_get_owner(policy->session);
 
-done:
-       (*data->callback)(data->session, config, data->user_data, err);
+       err = connman_dbus_get_selinux_context(connection, owner,
+                                               selinux_context_reply, cbd);
+       if (err == 0) {
+               /*
+                * We are able to ask for a SELinux context. Let's defer the
+                * creation of the session config until we get the answer
+                * from D-Bus.
+                */
+               return;
+       }
 
-       g_free(data);
-       g_free(ident);
+       finish_create(policy, cb, cbd->user_data);
+       g_free(cbd);
+
+       return;
+
+err:
+       failed_create(policy, cb, cbd->user_data, err);
+       g_free(cbd);
+       g_free(groups);
 }
 
 static int policy_local_create(struct connman_session *session,
-                               connman_session_config_cb callback,
+                               connman_session_config_func_t cb,
                                void *user_data)
 {
-       struct create_data *data;
+       struct cb_data *cbd = cb_data_new(cb, user_data);
+       struct policy_config *policy;
        const char *owner;
        int err;
 
        DBG("session %p", session);
 
-       data = g_try_new0(struct create_data, 1);
-       if (data == NULL)
-               return -ENOMEM;
+       policy = g_new0(struct policy_config, 1);
+       policy->config = connman_session_create_default_config();
+       policy->session = session;
 
-       data->session = session;
-       data->callback = callback;
-       data->user_data = user_data;
+       cbd->data = policy;
 
        owner = connman_session_get_owner(session);
 
-       err = connman_dbus_get_selinux_context(connection, owner,
-                                       selinux_context_reply,
-                                       data);
+       err = connman_dbus_get_connection_unix_user(connection, owner,
+                                               get_uid_reply, cbd);
        if (err < 0) {
-               connman_error("Could not get SELinux context");
-               g_free(data);
+               connman_error("Could not get UID");
+               cleanup_config(policy);
+               g_free(cbd);
                return err;
        }
 
@@ -241,12 +374,10 @@ static void policy_local_destroy(struct connman_session *session)
        DBG("session %p", session);
 
        policy = g_hash_table_lookup(session_hash, session);
-       if (policy == NULL)
+       if (!policy)
                return;
 
        g_hash_table_remove(session_hash, session);
-       policy->session = NULL;
-       policy_unref(policy);
 }
 
 static struct connman_session_policy session_policy_local = {
@@ -261,11 +392,9 @@ static int load_keyfile(const char *pathname, GKeyFile **keyfile)
        GError *error = NULL;
        int err;
 
-       DBG("Loading %s", pathname);
-
        *keyfile = g_key_file_new();
 
-       if (g_key_file_load_from_file(*keyfile, pathname, 0, &error) == FALSE)
+       if (!g_key_file_load_from_file(*keyfile, pathname, 0, &error))
                goto err;
 
        return 0;
@@ -286,62 +415,53 @@ err:
        return err;
 }
 
-static int load_policy(struct policy_data *policy)
+static int load_policy(GKeyFile *keyfile, const char *groupname,
+                       struct policy_group *group)
 {
-       struct connman_session_config *config = policy->config;
-       GKeyFile *keyfile;
-       char *pathname;
+       struct connman_session_config *config = group->config;
        char *str, **tokens;
        int i, err = 0;
 
-       connman_session_set_default_config(config);
+       group->selinux = g_key_file_get_string(keyfile, groupname,
+                                               "selinux", NULL);
 
-       pathname = g_strdup_printf("%s/%s", POLICYDIR, policy->ident);
-       if(pathname == NULL)
-               return -ENOMEM;
+       group->gid = g_key_file_get_string(keyfile, groupname,
+                                               "gid", NULL);
 
-       err = load_keyfile(pathname, &keyfile);
-       if (err < 0) {
-               g_free(pathname);
+       group->uid = g_key_file_get_string(keyfile, groupname,
+                                               "uid", NULL);
 
-               if (err == -ENOENT) {
-                       /* Ignore empty files */
-                       return 0;
-               }
-
-               return err;
-       }
+       if (!group->selinux && !group->gid && !group->uid)
+               return -EINVAL;
 
-       config->priority = g_key_file_get_boolean(keyfile, "Default",
+       config->priority = g_key_file_get_boolean(keyfile, groupname,
                                                "Priority", NULL);
 
-       str = g_key_file_get_string(keyfile, "Default", "RoamingPolicy",
+       str = g_key_file_get_string(keyfile, groupname, "RoamingPolicy",
                                NULL);
-       if (str != NULL) {
+       if (str) {
                config->roaming_policy = connman_session_parse_roaming_policy(str);
                g_free(str);
        }
 
-       str = g_key_file_get_string(keyfile, "Default", "ConnectionType",
+       str = g_key_file_get_string(keyfile, groupname, "ConnectionType",
                                NULL);
-       if (str != NULL) {
+       if (str) {
                config->type = connman_session_parse_connection_type(str);
                g_free(str);
        }
 
-       config->ecall = g_key_file_get_boolean(keyfile, "Default",
+       config->ecall = g_key_file_get_boolean(keyfile, groupname,
                                                "EmergencyCall", NULL);
 
-       g_slist_free(config->allowed_bearers);
-       config->allowed_bearers = NULL;
-
-       str = g_key_file_get_string(keyfile, "Default", "AllowedBearers",
+       str = g_key_file_get_string(keyfile, groupname, "AllowedBearers",
                                NULL);
-
-       if (str != NULL) {
+       if (str) {
+               g_slist_free(config->allowed_bearers);
+               config->allowed_bearers = NULL;
                tokens = g_strsplit(str, " ", 0);
 
-               for (i = 0; tokens[i] != NULL; i++) {
+               for (i = 0; tokens[i]; i++) {
                        err = connman_session_parse_bearers(tokens[i],
                                        &config->allowed_bearers);
                        if (err < 0)
@@ -350,126 +470,276 @@ static int load_policy(struct policy_data *policy)
 
                g_free(str);
                g_strfreev(tokens);
-       } else {
-               config->allowed_bearers = g_slist_append(NULL,
-                               GINT_TO_POINTER(CONNMAN_SERVICE_TYPE_UNKNOWN));
-               if (config->allowed_bearers == NULL)
-                       err = -ENOMEM;
        }
 
-       g_key_file_free(keyfile);
-       g_free(pathname);
+       DBG("group %p selinux %s uid %s gid %s", group, group->selinux,
+               group->uid, group->gid);
 
        return err;
 }
 
-static void update_session(struct connman_session *session)
+static void update_session(struct policy_config *policy)
+{
+       DBG("policy %p session %p", policy, policy->session);
+
+       if (!policy->session)
+               return;
+
+       if (connman_session_config_update(policy->session) < 0)
+               connman_session_destroy(policy->session);
+}
+
+static void set_default_config(gpointer user_data)
 {
-       if (connman_session_config_update(session) < 0)
-               connman_session_destroy(session);
+       struct policy_config *policy = user_data;
+
+       connman_session_set_default_config(policy->config);
+       policy->group = NULL;
+       update_session(policy);
 }
 
-static void remove_policy(struct policy_data *policy)
+static void cleanup_config(gpointer user_data)
 {
-       connman_bool_t update = FALSE;
-       int err;
+       struct policy_config *policy = user_data;
 
-       if (policy->session != NULL)
-               update = TRUE;
+       DBG("policy %p group %p", policy, policy->group);
 
-       policy_unref(policy);
+       if (policy->group)
+               policy->group->sessions =
+                       g_slist_remove(policy->group->sessions, policy);
 
-       if (update == FALSE)
-               return;
+       g_slist_free(policy->config->allowed_bearers);
+       g_free(policy->config->id);
+       g_free(policy->config);
+       g_free(policy->selinux_context);
+       g_free(policy->selinux);
+       g_free(policy->uid);
+       g_slist_free_full(policy->gids, g_free);
+       g_free(policy);
+}
 
-       err = connman_session_set_default_config(policy->config);
-       if (err < 0) {
-               connman_session_destroy(policy->session);
-               return;
-       }
+static void cleanup_group(gpointer user_data)
+{
+       struct policy_group *group = user_data;
+
+       DBG("group %p", group);
+
+       g_slist_free_full(group->sessions, set_default_config);
+
+       g_slist_free(group->config->allowed_bearers);
+       g_free(group->config->id);
+       g_free(group->config);
+       if (group->selinux)
+               g_hash_table_remove(selinux_hash, group->selinux);
+       if (group->uid)
+               g_hash_table_remove(uid_hash, group->uid);
+       if (group->gid)
+               g_hash_table_remove(gid_hash, group->gid);
+       g_free(group->selinux);
+       g_free(group->uid);
+       g_free(group->gid);
+       g_free(group);
+}
+
+static void cleanup_file(gpointer user_data)
+{
+       struct policy_file *file = user_data;
+
+       DBG("file %p", file);
 
-       update_session(policy->session);
+       g_slist_free_full(file->groups, cleanup_group);
+       g_free(file);
 }
 
-static void notify_handler(struct inotify_event *event,
-                                        const char *ident)
+static void recheck_sessions(void)
 {
-       struct policy_data *policy;
+       GHashTableIter iter;
+       gpointer value, key;
+       struct policy_group *group = NULL;
+       GSList *list;
+
+       g_hash_table_iter_init(&iter, session_hash);
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
+               struct policy_config *policy = value;
+
+               if (policy->group)
+                       continue;
+
+               if (policy->selinux)
+                       group = g_hash_table_lookup(selinux_hash,
+                                                       policy->selinux);
+               if (group) {
+                       policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM;
+                       g_free(policy->config->id);
+                       policy->config->id = g_strdup(policy->selinux_context);
+                       update_session(policy);
+                       continue;
+               }
 
-       if (ident == NULL)
-               return;
+               group = g_hash_table_lookup(uid_hash, policy->uid);
+               if (group) {
+                       set_policy(policy, group);
 
-       policy = g_hash_table_lookup(policy_hash, ident);
+                       policy->config->id_type = CONNMAN_SESSION_ID_TYPE_UID;
+                       g_free(policy->config->id);
+                       policy->config->id = g_strdup(policy->uid);
+                       update_session(policy);
+                       continue;
+               }
 
-       if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
-               connman_info("Policy added for '%s'", ident);
+               for (list = policy->gids; list; list = list->next) {
+                       char *gid = list->data;
+                       group = g_hash_table_lookup(gid_hash, gid);
+                       if (group) {
+                               set_policy(policy, group);
 
-               if (policy != NULL)
-                       policy_ref(policy);
-               else
-                       policy = create_policy(ident);
+                               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_GID;
+                               g_free(policy->config->id);
+                               policy->config->id = g_strdup(gid);
+                               update_session(policy);
+                       }
+               }
        }
+}
 
-       if (policy == NULL)
-               return;
+static int load_file(const char *filename, struct policy_file *file)
+{
+       GKeyFile *keyfile;
+       struct policy_group *group;
+       char **groupnames;
+       char *pathname;
+       int err = 0, i;
 
-       if (event->mask & IN_MODIFY) {
-               connman_info("Policy modifed for '%s'", ident);
+       DBG("%s", filename);
 
-               if (load_policy(policy) < 0) {
-                       remove_policy(policy);
-                       return;
+       pathname = g_strdup_printf("%s/%s", POLICYDIR, filename);
+       err = load_keyfile(pathname, &keyfile);
+       g_free(pathname);
+
+       if (err < 0)
+               return err;
+
+       groupnames = g_key_file_get_groups(keyfile, NULL);
+
+       for (i = 0; groupnames[i]; i++) {
+               group = g_new0(struct policy_group, 1);
+               group->config = connman_session_create_default_config();
+
+               err = load_policy(keyfile, groupnames[i], group);
+               if (err < 0) {
+                       g_free(group->config);
+                       g_free(group);
+                       break;
                }
-       }
+               if (group->selinux)
+                       g_hash_table_replace(selinux_hash, group->selinux, group);
 
-       if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
-               connman_info("Policy deleted for '%s'", ident);
+               if (group->uid)
+                       g_hash_table_replace(uid_hash, group->uid, group);
 
-               remove_policy(policy);
-               return;
+               if (group->gid)
+                       g_hash_table_replace(gid_hash, group->gid, group);
+
+               file->groups = g_slist_prepend(file->groups, group);
        }
 
-       if (policy->session != NULL)
-               update_session(policy->session);
+       g_strfreev(groupnames);
+
+       if (err < 0)
+               g_slist_free_full(file->groups, cleanup_group);
+
+       g_key_file_free(keyfile);
+
+       return err;
+}
+
+static bool is_filename_valid(const char *filename)
+{
+       if (!filename)
+               return false;
+
+       if (filename[0] == '.')
+               return false;
+
+       return g_str_has_suffix(filename, ".policy");
 }
 
 static int read_policies(void)
 {
        GDir *dir;
-       int err = 0;
+       const gchar *filename;
+       struct policy_file *file;
 
        DBG("");
 
        dir = g_dir_open(POLICYDIR, 0, NULL);
-       if (dir != NULL) {
-               const gchar *file;
+       if (!dir)
+               return -EINVAL;
 
-               while ((file = g_dir_read_name(dir)) != NULL) {
-                       struct policy_data *policy;
-
-                       policy = create_policy(file);
-                       if (policy == NULL) {
-                               err = -ENOMEM;
-                               break;
-                       }
+       while ((filename = g_dir_read_name(dir))) {
+               if (!is_filename_valid(filename))
+                       continue;
 
-                       err = load_policy(policy);
-                       if (err < 0)
-                               break;
+               file = g_new0(struct policy_file, 1);
+               if (load_file(filename, file) < 0) {
+                       g_free(file);
+                       continue;
                }
 
-               g_dir_close(dir);
+               g_hash_table_replace(file_hash, g_strdup(filename), file);
        }
 
-       return err;
+       g_dir_close(dir);
+
+       return 0;
+}
+
+
+static void notify_handler(struct inotify_event *event,
+                                        const char *filename)
+{
+       struct policy_file *file;
+
+       DBG("event %x file %s", event->mask, filename);
+
+       if (event->mask & IN_CREATE)
+               return;
+
+       if (!is_filename_valid(filename))
+               return;
+
+       /*
+        * load_file() will modify the global selinux/uid/gid hash
+        * tables. We need to remove the old entries first before
+        * else the table points to the wrong entries.
+        */
+       g_hash_table_remove(file_hash, filename);
+
+       if (event->mask & (IN_DELETE | IN_MOVED_FROM))
+               return;
+
+       if (event->mask & (IN_MOVED_TO | IN_MODIFY)) {
+               connman_info("Policy update for '%s'", filename);
+
+               file = g_new0(struct policy_file, 1);
+               if (load_file(filename, file) < 0) {
+                       g_free(file);
+                       return;
+               }
+
+               g_hash_table_replace(file_hash, g_strdup(filename), file);
+               recheck_sessions();
+       }
 }
 
 static int session_policy_local_init(void)
 {
        int err;
 
+       DBG("");
+
        /* If the dir doesn't exist, create it */
-       if (g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR) == FALSE) {
+       if (!g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR)) {
                if (mkdir(POLICYDIR, MODE) < 0) {
                        if (errno != EEXIST)
                                return -errno;
@@ -477,35 +747,30 @@ static int session_policy_local_init(void)
        }
 
        connection = connman_dbus_get_connection();
-       if (connection == NULL)
+       if (!connection)
                return -EIO;
 
+       file_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                       g_free, cleanup_file);
        session_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
-                                               NULL, NULL);
-       if (session_hash == NULL) {
-               err = -ENOMEM;
-               goto err;
-       }
-
-       policy_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                       NULL, cleanup_policy);
-       if (policy_hash == NULL) {
-               err = -ENOMEM;
-               goto err;
-       }
+                                               NULL, cleanup_config);
+       selinux_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                       NULL, NULL);
+       uid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                       NULL, NULL);
+       gid_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                       NULL, NULL);
 
        err = connman_inotify_register(POLICYDIR, notify_handler);
        if (err < 0)
                goto err;
 
-       err = read_policies();
-       if (err < 0)
-               goto err_notify;
-
        err = connman_session_policy_register(&session_policy_local);
        if (err < 0)
                goto err_notify;
 
+       read_policies();
+
        return 0;
 
 err_notify:
@@ -513,10 +778,20 @@ err_notify:
        connman_inotify_unregister(POLICYDIR, notify_handler);
 
 err:
-       if (session_hash != NULL)
+       if (file_hash)
+               g_hash_table_destroy(file_hash);
+
+       if (session_hash)
                g_hash_table_destroy(session_hash);
-       if (policy_hash != NULL)
-               g_hash_table_destroy(policy_hash);
+
+       if (selinux_hash)
+               g_hash_table_destroy(selinux_hash);
+
+       if (uid_hash)
+               g_hash_table_destroy(uid_hash);
+
+       if (gid_hash)
+               g_hash_table_destroy(gid_hash);
 
        connman_session_policy_unregister(&session_policy_local);
 
@@ -527,8 +802,13 @@ err:
 
 static void session_policy_local_exit(void)
 {
+       DBG("");
+
+       g_hash_table_destroy(file_hash);
        g_hash_table_destroy(session_hash);
-       g_hash_table_destroy(policy_hash);
+       g_hash_table_destroy(selinux_hash);
+       g_hash_table_destroy(uid_hash);
+       g_hash_table_destroy(gid_hash);
 
        connman_session_policy_unregister(&session_policy_local);