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 4b17a4c..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 | \
@@ -49,12 +54,10 @@ static DBusConnection *connection;
 static GHashTable *file_hash;    /* (filename, policy_file) */
 static GHashTable *session_hash; /* (connman_session, policy_config) */
 
-/* Global lookup table for mapping sessions to policies */
+/* Global lookup tables for mapping sessions to policies */
 static GHashTable *selinux_hash; /* (lsm context, policy_group) */
-
-struct create_data {
-       struct connman_session *session;
-};
+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
@@ -63,13 +66,15 @@ struct create_data {
 struct policy_file {
        /*
         * A valid file is a keyfile with one ore more groups. All
-        * groups are keept in this list.
+        * groups are kept in this list.
         */
        GSList *groups;
 };
 
 struct policy_group {
        char *selinux;
+       char *uid;
+       char *gid;
 
        /*
         * Each policy_group owns a config and is not shared with
@@ -85,6 +90,9 @@ struct policy_group {
 /* 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;
@@ -126,7 +134,7 @@ static char *parse_selinux_type(const char *context)
 
        /*
         * 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
@@ -137,7 +145,7 @@ static char *parse_selinux_type(const 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.
         */
@@ -156,17 +164,63 @@ static char *parse_selinux_type(const char *context)
        return ident;
 }
 
-static struct policy_config *create_policy(void)
+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_config *policy;
+       struct policy_group *group = NULL;
+       GSList *list;
 
-       policy = g_new0(struct policy_config, 1);
+       if (policy->selinux)
+               group = g_hash_table_lookup(selinux_hash, policy->selinux);
 
-       DBG("policy %p", policy);
+       if (group) {
+               set_policy(policy, group);
 
-       policy->config = connman_session_create_default_config();
+               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM;
+               policy->config->id = g_strdup(policy->selinux_context);
+               goto done;
+       }
+
+       if (policy->uid)
+               group = g_hash_table_lookup(uid_hash, policy->uid);
+
+       if (group) {
+               set_policy(policy, group);
+
+               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_UID;
+               policy->config->id = g_strdup(policy->uid);
+               goto done;
+       }
+
+       for (list = policy->gids; list; list = list->next) {
+               char *gid = list->data;
+
+               group = g_hash_table_lookup(gid_hash, gid);
+               if (!group)
+                       continue;
+
+               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 failed_create(struct policy_config *policy,
+                       connman_session_config_func_t cb,
+                       void *user_data, int err)
+{
+       (*cb)(policy->session, NULL, user_data, err);
+
+       cleanup_config(policy);
 }
 
 static void selinux_context_reply(const unsigned char *context, void *user_data,
@@ -174,68 +228,138 @@ static void selinux_context_reply(const unsigned char *context, void *user_data,
 {
        struct cb_data *cbd = user_data;
        connman_session_config_func_t cb = cbd->cb;
-       struct create_data *data = cbd->data;
-       struct policy_config *policy;
-       struct policy_group *group;
-       struct connman_session_config *config = NULL;
+       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;
+       }
 
-       DBG("SELinux context %s", context);
-
-       ident = parse_selinux_type((const char*)context);
-       if (ident == NULL) {
-               err = -EINVAL;
+       if (err < 0) {
+               failed_create(policy, cb, cbd->user_data, err);
                goto done;
        }
 
-       policy = create_policy();
-       policy->selinux = g_strdup(ident);
-       policy->session = data->session;
+       DBG("SELinux context %s", context);
 
-       group = g_hash_table_lookup(selinux_hash, policy->selinux);
-       if (group != NULL)
-               set_policy(policy, group);
+       policy->selinux_context = g_strdup((const char *)context);
+       ident = parse_selinux_type(policy->selinux_context);
+       if (ident)
+               policy->selinux = g_strdup(ident);
 
-       g_hash_table_replace(session_hash, data->session, policy);
-       config = policy->config;
+       finish_create(policy, cb, cbd->user_data);
 
 done:
-       (*cb)(data->session, config, cbd->user_data, err);
-
        g_free(cbd);
-       g_free(data);
        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);
+
+       owner = connman_session_get_owner(policy->session);
+
+       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;
+       }
+
+       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_func_t cb,
                                void *user_data)
 {
        struct cb_data *cbd = cb_data_new(cb, user_data);
-       struct create_data *data;
+       struct policy_config *policy;
        const char *owner;
        int err;
 
        DBG("session %p", session);
 
-       data = g_new0(struct create_data, 1);
-       cbd->data = data;
+       policy = g_new0(struct policy_config, 1);
+       policy->config = connman_session_create_default_config();
+       policy->session = session;
 
-       data->session = session;
+       cbd->data = policy;
 
        owner = connman_session_get_owner(session);
 
-       err = connman_dbus_get_selinux_context(connection, owner,
-                                       selinux_context_reply,
-                                       cbd);
+       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;
        }
@@ -250,7 +374,7 @@ 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);
@@ -270,7 +394,7 @@ static int load_keyfile(const char *pathname, GKeyFile **keyfile)
 
        *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;
@@ -300,7 +424,14 @@ static int load_policy(GKeyFile *keyfile, const char *groupname,
 
        group->selinux = g_key_file_get_string(keyfile, groupname,
                                                "selinux", NULL);
-       if (group->selinux == NULL)
+
+       group->gid = g_key_file_get_string(keyfile, groupname,
+                                               "gid", NULL);
+
+       group->uid = g_key_file_get_string(keyfile, groupname,
+                                               "uid", NULL);
+
+       if (!group->selinux && !group->gid && !group->uid)
                return -EINVAL;
 
        config->priority = g_key_file_get_boolean(keyfile, groupname,
@@ -308,14 +439,14 @@ static int load_policy(GKeyFile *keyfile, const char *groupname,
 
        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, groupname, "ConnectionType",
                                NULL);
-       if (str != NULL) {
+       if (str) {
                config->type = connman_session_parse_connection_type(str);
                g_free(str);
        }
@@ -325,10 +456,12 @@ static int load_policy(GKeyFile *keyfile, const char *groupname,
 
        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)
@@ -339,15 +472,17 @@ static int load_policy(GKeyFile *keyfile, const char *groupname,
                g_strfreev(tokens);
        }
 
-       DBG("group %p selinux %s", group, group->selinux);
+       DBG("group %p selinux %s uid %s gid %s", group, group->selinux,
+               group->uid, group->gid);
 
        return err;
 }
+
 static void update_session(struct policy_config *policy)
 {
        DBG("policy %p session %p", policy, policy->session);
 
-       if (policy->session == NULL)
+       if (!policy->session)
                return;
 
        if (connman_session_config_update(policy->session) < 0)
@@ -369,13 +504,17 @@ static void cleanup_config(gpointer user_data)
 
        DBG("policy %p group %p", policy, policy->group);
 
-       if (policy->group != NULL)
+       if (policy->group)
                policy->group->sessions =
                        g_slist_remove(policy->group->sessions, policy);
 
        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);
 }
 
@@ -388,10 +527,17 @@ static void cleanup_group(gpointer user_data)
        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 != NULL)
+       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);
 }
 
@@ -410,18 +556,48 @@ static void recheck_sessions(void)
        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) == TRUE) {
+       while (g_hash_table_iter_next(&iter, &key, &value)) {
                struct policy_config *policy = value;
 
-               if (policy->group != NULL)
+               if (policy->group)
                        continue;
 
-               group = g_hash_table_lookup(selinux_hash, policy->selinux);
-               if (group != NULL) {
+               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;
+               }
+
+               group = g_hash_table_lookup(uid_hash, policy->uid);
+               if (group) {
                        set_policy(policy, group);
+
+                       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;
+               }
+
+               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);
+
+                               policy->config->id_type = CONNMAN_SESSION_ID_TYPE_GID;
+                               g_free(policy->config->id);
+                               policy->config->id = g_strdup(gid);
+                               update_session(policy);
+                       }
                }
        }
 }
@@ -445,9 +621,9 @@ static int load_file(const char *filename, struct policy_file *file)
 
        groupnames = g_key_file_get_groups(keyfile, NULL);
 
-       for (i = 0; groupnames[i] != NULL; i++) {
+       for (i = 0; groupnames[i]; i++) {
                group = g_new0(struct policy_group, 1);
-               group->config = g_new0(struct connman_session_config, 1);
+               group->config = connman_session_create_default_config();
 
                err = load_policy(keyfile, groupnames[i], group);
                if (err < 0) {
@@ -455,7 +631,14 @@ static int load_file(const char *filename, struct policy_file *file)
                        g_free(group);
                        break;
                }
-               g_hash_table_replace(selinux_hash, group->selinux, group);
+               if (group->selinux)
+                       g_hash_table_replace(selinux_hash, group->selinux, group);
+
+               if (group->uid)
+                       g_hash_table_replace(uid_hash, group->uid, group);
+
+               if (group->gid)
+                       g_hash_table_replace(gid_hash, group->gid, group);
 
                file->groups = g_slist_prepend(file->groups, group);
        }
@@ -470,18 +653,18 @@ static int load_file(const char *filename, struct policy_file *file)
        return err;
 }
 
-static connman_bool_t is_filename_valid(const char *filename)
+static bool is_filename_valid(const char *filename)
 {
-       if (filename == NULL)
-               return FALSE;
+       if (!filename)
+               return false;
 
        if (filename[0] == '.')
-               return FALSE;
+               return false;
 
        return g_str_has_suffix(filename, ".policy");
 }
 
-static int read_policies()
+static int read_policies(void)
 {
        GDir *dir;
        const gchar *filename;
@@ -490,11 +673,11 @@ static int read_policies()
        DBG("");
 
        dir = g_dir_open(POLICYDIR, 0, NULL);
-       if (dir == NULL)
+       if (!dir)
                return -EINVAL;
 
-       while ((filename = g_dir_read_name(dir)) != NULL) {
-               if (is_filename_valid(filename) == FALSE)
+       while ((filename = g_dir_read_name(dir))) {
+               if (!is_filename_valid(filename))
                        continue;
 
                file = g_new0(struct policy_file, 1);
@@ -522,7 +705,7 @@ static void notify_handler(struct inotify_event *event,
        if (event->mask & IN_CREATE)
                return;
 
-       if (is_filename_valid(filename) == FALSE)
+       if (!is_filename_valid(filename))
                return;
 
        /*
@@ -556,7 +739,7 @@ static int session_policy_local_init(void)
        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;
@@ -564,7 +747,7 @@ 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,
@@ -573,6 +756,10 @@ static int session_policy_local_init(void)
                                                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)
@@ -591,15 +778,21 @@ err_notify:
        connman_inotify_unregister(POLICYDIR, notify_handler);
 
 err:
-       if (file_hash != NULL)
+       if (file_hash)
                g_hash_table_destroy(file_hash);
 
-       if (session_hash != NULL)
+       if (session_hash)
                g_hash_table_destroy(session_hash);
 
-       if (selinux_hash != NULL)
+       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);
 
        dbus_connection_unref(connection);
@@ -614,6 +807,8 @@ static void session_policy_local_exit(void)
        g_hash_table_destroy(file_hash);
        g_hash_table_destroy(session_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);