X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=plugins%2Fsession_policy_local.c;h=9beb0980e62bbf076747765a2f4900fabe2dc7fd;hb=33cdd54e34fcea201488aed301a31a204520b287;hp=dfaeaea6897b65d5a2e7bd7f9a386f30e2a5573b;hpb=c6025eca557d82d9514108ede6ba0aa266746e64;p=platform%2Fupstream%2Fconnman.git diff --git a/plugins/session_policy_local.c b/plugins/session_policy_local.c old mode 100644 new mode 100755 index dfaeaea..9beb098 --- a/plugins/session_policy_local.c +++ b/plugins/session_policy_local.c @@ -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 #include #include +#include +#include +#include #include @@ -39,6 +42,8 @@ #include #include +#include "src/shared/util.h" + #define POLICYDIR STORAGEDIR "/session_policy_local" #define MODE (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | \ @@ -46,36 +51,86 @@ 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; +/* 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 keept 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; }; -static void cleanup_policy(gpointer user_data) +/* 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 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 @@ -95,15 +150,9 @@ static char *parse_ident(const unsigned char *context) * 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; } @@ -111,91 +160,180 @@ 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(); - policy->refcount = 1; - policy->ident = g_strdup(ident); + policy->config->id_type = CONNMAN_SESSION_ID_TYPE_LSM; + policy->config->id = g_strdup(policy->selinux_context); + goto done; + } - g_hash_table_replace(policy_hash, policy->ident, policy); + if (policy->uid) + group = g_hash_table_lookup(uid_hash, policy->uid); - return policy; -} + if (group) { + set_policy(policy, group); -static struct policy_data *policy_ref(struct policy_data *policy) -{ - DBG("%p %s ref %d", policy, policy->ident, policy->refcount + 1); + 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; - __sync_fetch_and_add(&policy->refcount, 1); + group = g_hash_table_lookup(gid_hash, gid); + if (!group) + continue; - return policy; + set_policy(policy, group); + + 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); + (*cb)(policy->session, NULL, user_data, err); - if (__sync_fetch_and_sub(&policy->refcount, 1) != 1) - return; - - 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 cb_data *cbd = user_data; connman_session_config_func_t cb = cbd->cb; - struct create_data *data = cbd->data; - struct policy_data *policy; - 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; + } - 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: - (*cb)(data->session, config, cbd->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; + } + finish_create(policy, cb, cbd->user_data); g_free(cbd); - g_free(data); - g_free(ident); + + 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, @@ -203,27 +341,25 @@ static int policy_local_create(struct connman_session *session, 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_try_new0(struct create_data, 1); - if (data == NULL) - return -ENOMEM; - 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; } @@ -238,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 = { @@ -258,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; @@ -283,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); - 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) @@ -349,123 +472,274 @@ static int load_policy(struct policy_data *policy) g_strfreev(tokens); } - 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) { - if (connman_session_config_update(session) < 0) - connman_session_destroy(session); + 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 remove_policy(struct policy_data *policy) +static void set_default_config(gpointer user_data) { - connman_bool_t update = FALSE; + struct policy_config *policy = user_data; + + connman_session_set_default_config(policy->config); + policy->group = NULL; + update_session(policy); +} - if (policy->session != NULL) - update = TRUE; +static void cleanup_config(gpointer user_data) +{ + struct policy_config *policy = user_data; - policy_unref(policy); + DBG("policy %p group %p", policy, policy->group); - if (update == FALSE) - return; + if (policy->group) + policy->group->sessions = + g_slist_remove(policy->group->sessions, policy); - connman_session_set_default_config(policy->config); - update_session(policy->session); + 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); } -static void notify_handler(struct inotify_event *event, - const char *ident) +static void cleanup_group(gpointer user_data) { - struct policy_data *policy; - int err; + 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); +} - if (ident == NULL) - return; +static void cleanup_file(gpointer user_data) +{ + struct policy_file *file = user_data; - policy = g_hash_table_lookup(policy_hash, ident); + DBG("file %p", file); - if (event->mask & (IN_CREATE | IN_MOVED_TO)) { - connman_info("Policy added for '%s'", ident); + g_slist_free_full(file->groups, cleanup_group); + g_free(file); +} - if (policy != NULL) - policy_ref(policy); - else - policy = create_policy(ident); +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)) { + 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; + } - err = load_policy(policy); - if (err < 0) { - connman_warn("Loading policy file '%s' failed with %s", - ident, strerror(-err)); - return; + 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); + } } } +} - 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; + + DBG("%s", filename); + + pathname = g_strdup_printf("%s/%s", POLICYDIR, filename); + err = load_keyfile(pathname, &keyfile); + g_free(pathname); + + if (err < 0) + return err; - if (event->mask & IN_MODIFY) { - connman_info("Policy modifed for '%s'", ident); + groupnames = g_key_file_get_groups(keyfile, NULL); - err = load_policy(policy); + 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) { - connman_warn("Loading policy file '%s' failed with %s", - ident, strerror(-err)); - return; + 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; - - while ((file = g_dir_read_name(dir)) != NULL) { - struct policy_data *policy; + if (!dir) + return -EINVAL; - 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; @@ -473,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: @@ -509,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); @@ -523,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);