blob: f70dc97ea2674186ba64b687b6a2000ed17e59d9 [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2009 Intel Corporation. All rights reserved.
*
* 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
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <glib.h>
#include <gdbus.h>
#include <connman/assert.h>
#include "connman.h"
#define PROFILE_DEFAULT_IDENT "default"
#define PROFILE_MAX 3 /* 2 is probably sufficient */
#define PROFILE_DEFAULT_PORTAL_URL \
"http://clients3.google.com/generate_204"
#define _DBG_PROFILE(fmt, arg...) DBG(DBG_PROFILE, fmt, ## arg)
struct connman_profile {
struct connman_storage_ident ident;
guint changed_timeout;
char *path;
char *name;
connman_bool_t offlinemode;
connman_bool_t arpgateway;
char *country;
uint32_t checkportal;
char *portal_url;
};
static GHashTable *profile_hash = NULL;
static struct connman_profile *profile_stack[PROFILE_MAX];
static int cur_profile = -1;
static struct connman_storage_ident default_ident = {
.ident = PROFILE_DEFAULT_IDENT
};
static struct connman_profile *default_profile = NULL;
static guint changed_timeout = 0; /* for NULL profile handling */
static DBusConnection *connection = NULL;
/* NB: everything but UNKONWN and VPN */
static uint32_t profile_default_checkportal_mask =
(0xffffffff &~
((1<<CONNMAN_SERVICE_TYPE_UNKNOWN) | (1<<CONNMAN_SERVICE_TYPE_VPN)));
static const char *__profile_name(const struct connman_profile *profile)
{
return (profile == NULL) ? "(null)" : profile->path;
}
/*
* Loading/Saving objects.
*
* Service objects go to the profile they are pinned to (typically
* the active profile at the time they were created but this can be
* changed, e.g. from private -> global).
*
* Device and ipconfig objects go in the global profile (if any).
* This ensures that enable/disable state is maintained between
* users (and reboots); or possibly discarded (e.g. for testing).
*
* Likewise global state like offline mode is stored in the global
* profile (see above).
*/
/*
* Return the active profile; it's on the top of the stack.
*/
static inline struct connman_profile *active_profile(void)
{
return cur_profile >= 0 ? profile_stack[cur_profile] : NULL;
}
/*
* Return the global profile; it's top-most non-user profile.
*/
static struct connman_profile *global_profile(void)
{
/* TODO(sleffler) cheat for now */
return default_profile != NULL ? default_profile : NULL;
}
static int ident_equal(const struct connman_storage_ident *a,
const struct connman_storage_ident *b)
{
return (g_strcmp0(a->user, b->user) == 0 &&
g_strcmp0(a->ident, b->ident) == 0);
}
static void append_path(gpointer key, gpointer value, gpointer user_data)
{
struct connman_profile *profile = value;
DBusMessageIter *iter = user_data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
&profile->path);
}
void __connman_profile_list(DBusMessageIter *iter, void *arg)
{
g_hash_table_foreach(profile_hash, append_path, iter);
}
static void profiles_changed(void)
{
connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "Profiles",
DBUS_TYPE_OBJECT_PATH, __connman_profile_list, NULL);
}
connman_bool_t __connman_profile_get_offlinemode(void)
{
struct connman_profile *profile = global_profile();
return (profile == NULL) ? FALSE : profile->offlinemode;
}
int __connman_profile_set_offlinemode(connman_bool_t offlinemode)
{
struct connman_profile *profile = global_profile();
int ret;
_DBG_PROFILE("offlinemode %d profile %s", offlinemode,
__profile_name(profile));
/* NB: always succeeds (ATM) */
ret = __connman_device_set_offlinemode(offlinemode);
if (ret != 0)
return ret;
/* TODO(sleffler) sallow even if no global profile? */
if (profile != NULL) {
/*
* OfflineMode is only saved to the default profile;
* this ensures it is preserved across user changes.
*/
if (profile->offlinemode == offlinemode)
return -EALREADY;
profile->offlinemode = offlinemode;
connman_dbus_send_property_changed_variant(
profile->path,
CONNMAN_PROFILE_INTERFACE, "OfflineMode",
DBUS_TYPE_BOOLEAN, &offlinemode);
__connman_storage_save_profile(profile);
}
return 0;
}
connman_bool_t connman_profile_get_arpgateway(void)
{
struct connman_profile *profile = global_profile();
return (profile == NULL) ? FALSE : profile->arpgateway;
}
int connman_profile_set_arpgateway(connman_bool_t arpgateway)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("arpgateway %d profile %s", arpgateway,
__profile_name(profile));
if (profile != NULL) {
if (profile->arpgateway == arpgateway)
return -EALREADY;
profile->arpgateway = arpgateway;
connman_dbus_send_property_changed_variant(
profile->path,
CONNMAN_PROFILE_INTERFACE, "ArpGateway",
DBUS_TYPE_BOOLEAN, &arpgateway);
__connman_storage_save_profile(profile);
}
return 0;
}
static void country_changed(struct connman_profile *profile)
{
connman_dbus_send_property_changed_variant(profile->path,
CONNMAN_PROFILE_INTERFACE, "Country",
DBUS_TYPE_STRING, &profile->country);
}
const char *connman_profile_get_country(void)
{
struct connman_profile *profile = global_profile();
return (profile == NULL) ? NULL : profile->country;
}
int connman_profile_set_country(const char *country)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("country %s profile %s", country, __profile_name(profile));
if (profile != NULL) {
if (g_strcmp0(profile->country, country) == 0)
return -EALREADY;
g_free(profile->country);
profile->country = g_strdup(country);
country_changed(profile);
__connman_storage_save_profile(profile);
}
__connman_notifier_country_changed(country);
return 0;
}
uint32_t __connman_profile_get_checkportal(void)
{
struct connman_profile *profile = global_profile();
return (profile == NULL) ?
profile_default_checkportal_mask : profile->checkportal;
}
int __connman_profile_set_checkportal(uint32_t checkportal)
{
struct connman_profile *profile = global_profile();
gchar *str;
_DBG_PROFILE("checkportal 0x%x profile %s", checkportal,
__profile_name(profile));
if (profile != NULL) {
/*
* CheckPortal is only saved to the global profile;
* this ensures it is preserved across user changes.
*/
if (profile->checkportal == checkportal)
return -EALREADY;
profile->checkportal = checkportal;
str = __connman_service_mask_to_list(checkportal);
if (str != NULL) {
connman_dbus_send_property_changed_variant(
profile->path,
CONNMAN_PROFILE_INTERFACE, "CheckPortalList",
DBUS_TYPE_STRING, &str);
g_free(str);
}
__connman_storage_save_profile(profile);
}
return 0;
}
const char *__connman_profile_get_portal_url(void)
{
struct connman_profile *profile = global_profile();
/* NB: always return something not NULL */
return (profile == NULL || profile->portal_url == NULL) ?
PROFILE_DEFAULT_PORTAL_URL : profile->portal_url;
}
int __connman_profile_set_portal_url(const char *url)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("url %s profile %s", url, __profile_name(profile));
/* NB: require "http" prefix (allows https too) */
if (url == NULL || g_str_has_prefix(url, "http") == FALSE)
return -EINVAL;
if (profile != NULL) {
if (g_strcmp0(profile->portal_url, url) == 0)
return -EALREADY;
g_free(profile->portal_url);
profile->portal_url = g_strdup(url);
connman_dbus_send_property_changed_variant(profile->path,
CONNMAN_PROFILE_INTERFACE, "PortalURL",
DBUS_TYPE_STRING, &url);
__connman_storage_save_profile(profile);
}
return 0;
}
static inline int load_continue(int err)
{
/* NB: ENXIO for no file, ESRCH for no entry */
return (err == -ENXIO || err == -ESRCH);
}
int __connman_profile_load_service(struct connman_service *service)
{
struct connman_profile *profile =
__connman_service_get_profile(service);
int err, i;
_DBG_PROFILE("service %p profile %s", service, __profile_name(profile));
if (profile != NULL)
return __connman_storage_load_service(service, &profile->ident);
/*
* Not bound to a profile yet, search the stack for an
* entry and if found bind the profile to the service.
*/
err = 0;
for (i = cur_profile; i >= 0; i--) {
profile = profile_stack[i];
err = __connman_storage_load_service(service, &profile->ident);
if (err == 0) {
_DBG_PROFILE("bind to profile %s", profile->path);
__connman_service_set_profile(service, profile);
return 0;
}
if (!load_continue(err))
break;
}
return err;
}
/*
* Search for the specified GUID in the profile stack and return
* the associated object path suitable for instantiating an instance
* of the associated object (currently only a connman_service).
*/
char *__connman_profile_find_guid(const char *guid)
{
char *ident = NULL;
int i;
_DBG_PROFILE("guid %s", guid);
for (i = cur_profile; i >= 0; i--) {
struct connman_profile *profile = profile_stack[i];
ident = __connman_storage_find_guid(guid, &profile->ident);
if (ident != NULL) {
_DBG_PROFILE("found %s in profile %s", ident,
profile->path);
break;
}
}
return ident;
}
/*
* NB: This is like __connman_profile_load_service but defaults
* the service to use the global profile. This is used for
* "device services" (see __connman_service_create_from_device).
*/
int __connman_profile_load_device_service(struct connman_service *service)
{
struct connman_profile *profile =
__connman_service_get_profile(service);
_DBG_PROFILE("service %p profile %s", service, __profile_name(profile));
if (profile == NULL) {
/*
* Not bound to a profile yet, bind to the global profile.
*/
profile = global_profile();
if (profile == NULL) {
_DBG_PROFILE("no global profile; cannot bind");
return -ESRCH;
}
_DBG_PROFILE("bind to profile %s", profile->path);
__connman_service_set_profile(service, profile);
}
return __connman_storage_load_service(service, &profile->ident);
}
int __connman_profile_save_service(struct connman_service *service)
{
struct connman_profile *profile =
__connman_service_get_profile(service);
_DBG_PROFILE("service %p profile %s", service, __profile_name(profile));
if (profile == NULL) {
/* not bound yet, bind to the active profile */
profile = active_profile();
_DBG_PROFILE("bind to profile %s", __profile_name(profile));
__connman_service_set_profile(service, profile);
}
return (profile == NULL) ? 0 :
__connman_storage_save_service(service, &profile->ident);
}
int __connman_profile_load_provider(struct connman_provider *provider)
{
struct connman_profile *profile =
__connman_provider_get_profile(provider);
int err, i;
_DBG_PROFILE("provider %p profile %s", provider,
__profile_name(profile));
if (profile != NULL)
return __connman_storage_load_provider(provider,
&profile->ident);
/*
* Not bound to a profile yet, search the stack for an
* entry and if found bind the profile to the provider.
*/
err = 0;
for (i = cur_profile; i >= 0; i--) {
profile = profile_stack[i];
err = __connman_storage_load_provider(provider, &profile->ident);
if (err == 0) {
_DBG_PROFILE("bind to profile %s", profile->path);
__connman_provider_set_profile(provider, profile);
return 0;
}
if (!load_continue(err))
break;
}
return err;
}
int __connman_profile_save_provider(struct connman_provider *provider)
{
struct connman_profile *profile =
__connman_provider_get_profile(provider);
_DBG_PROFILE("provider %p profile %s", provider,
__profile_name(profile));
if (profile == NULL) {
/* not bound yet, bind to the active profile */
profile = active_profile();
_DBG_PROFILE("bind to profile %s", profile->path);
__connman_provider_set_profile(provider, profile);
}
return (profile == NULL) ? 0 :
__connman_storage_save_provider(provider, &profile->ident);
}
int __connman_profile_load_device(struct connman_device *device)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("device %p profile %s", device, __profile_name(profile));
return (profile == NULL) ? 0 :
__connman_storage_load_device(device, &profile->ident);
}
int __connman_profile_save_device(struct connman_device *device)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("device %p profile %s", device, __profile_name(profile));
return (profile == NULL) ? 0:
__connman_storage_save_device(device, &profile->ident);
}
int __connman_profile_load_ipconfig(struct connman_ipconfig *ipconfig)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("ipconfig %p profile %s", ipconfig,
__profile_name(profile));
return (profile == NULL) ? 0:
__connman_storage_load_ipconfig(ipconfig, &profile->ident);
}
int __connman_profile_save_ipconfig(const struct connman_ipconfig *ipconfig)
{
struct connman_profile *profile = global_profile();
_DBG_PROFILE("ipconfig %p profile %s", ipconfig,
__profile_name(profile));
return (profile == NULL) ? 0 :
__connman_storage_save_ipconfig(ipconfig, &profile->ident);
}
int __connman_profile_append_hidden_ssids(GSList **hidden_ssids,
void (*append_hidden_ssids)(GKeyFile *keyfile, GSList **hidden_ssids))
{
int i;
_DBG_PROFILE("");
for (i = cur_profile; i >= 0; i--) {
const struct connman_profile *profile = profile_stack[i];
GKeyFile *keyfile;
keyfile = __connman_storage_open(&profile->ident);
if (keyfile != NULL) {
append_hidden_ssids(keyfile, hidden_ssids);
__connman_storage_close(&profile->ident, keyfile,
FALSE);
}
}
return 0;
}
/*
* Return the object path for the specified profile.
*/
const char *__connman_profile_get_path(const struct connman_profile *profile)
{
return profile != NULL ? profile->path : NULL;
}
/*
* Return the profile given an object path.
*/
struct connman_profile *__connman_profile_lookup_profile(const char *path)
{
return g_hash_table_lookup(profile_hash, path);
}
/*
* Return the active profile or NULL
*/
struct connman_profile *__connman_profile_active_profile(void)
{
return active_profile();
}
const struct connman_storage_ident *__connman_profile_active_ident(void)
{
struct connman_profile *profile = active_profile();
return profile != NULL ? &profile->ident : NULL;
}
/*
* Return the object path for the active profile or NULL
* if there is none.
*/
const char *__connman_profile_active_path(void)
{
struct connman_profile *profile = active_profile();
return profile != NULL ? profile->path : NULL;
}
/*
* Delete an entry in the specified profile.
*/
int __connman_profile_delete_entry(struct connman_profile *profile,
const char *group)
{
GKeyFile *keyfile;
gboolean status;
_DBG_PROFILE("profile %s group %s", __profile_name(profile), group);
keyfile = __connman_storage_open(&profile->ident);
if (keyfile == NULL) {
_DBG_PROFILE("cannot open key file");
return -EINVAL;
}
status = g_key_file_remove_group(keyfile, group, NULL);
__connman_storage_close(&profile->ident, keyfile, status);
if (status == FALSE) {
_DBG_PROFILE("cannot remove %s", group);
return -ENXIO;
}
return 0;
}
static void __clear_timeout(guint *pchanged_timeout)
{
if (*pchanged_timeout > 0) {
g_source_remove(*pchanged_timeout);
*pchanged_timeout = 0;
}
}
static void clear_timeout(struct connman_profile *profile)
{
if (profile != NULL)
__clear_timeout(&profile->changed_timeout);
else
__clear_timeout(&changed_timeout);
}
static void append_services(DBusMessageIter *iter, void *arg)
{
__connman_service_list(iter, arg);
}
static gboolean services_changed(gpointer user_data)
{
struct connman_profile *profile = user_data;
if (profile != NULL) {
profile->changed_timeout = 0;
connman_dbus_send_property_changed_array(profile->path,
CONNMAN_PROFILE_INTERFACE, "Services",
DBUS_TYPE_OBJECT_PATH, append_services, NULL);
} else
changed_timeout = 0;
connman_dbus_send_property_changed_array(CONNMAN_MANAGER_PATH,
CONNMAN_MANAGER_INTERFACE, "Services",
DBUS_TYPE_OBJECT_PATH, append_services, NULL);
return FALSE;
}
/*
* Handle changes to a profile. Generate PropertyChanged signals
* on Manager.Services and Profile.Services for the currently active
* profile. To minimize overhead requests may be coalesced using a
* 1 second delay on the signals.
*/
void __connman_profile_changed(struct connman_profile *profile,
gboolean delayed)
{
_DBG_PROFILE("profile %s%s changed_timeout %d",
__profile_name(profile), delayed ? " delayed" : "",
profile != NULL ? profile->changed_timeout : changed_timeout);
clear_timeout(profile);
if (delayed == TRUE) {
guint timeout = g_timeout_add_seconds(1,
services_changed, profile);
if (profile != NULL)
profile->changed_timeout = timeout;
else
changed_timeout = timeout;
} else
services_changed(profile);
}
int __connman_profile_add_device(struct connman_device *device)
{
_DBG_PROFILE("device %p", device);
return __connman_service_create_from_device(device);
}
int __connman_profile_remove_device(struct connman_device *device)
{
_DBG_PROFILE("device %p", device);
__connman_service_remove_from_device(device);
return 0;
}
int __connman_profile_add_network(struct connman_network *network)
{
_DBG_PROFILE("network %p", network);
return __connman_service_create_from_network(network);
}
int __connman_profile_update_network(struct connman_network *network)
{
_DBG_PROFILE("network %p", network);
__connman_service_update_from_network(network);
return 0;
}
int __connman_profile_remove_network(struct connman_network *network)
{
_DBG_PROFILE("network %p", network);
__connman_service_remove_from_network(network);
return 0;
}
/*
* Extract the service type from a profile group name
* and return the enumerated type value (if any). Return
* CONNMAN_SERVICE_TYPE_UNKNOWN for group names we don't
* care about.
*/
static int get_service_type(const char *gname)
{
char type[32]; /* NB: known large enough */
int i;
/* extract type from group name */
for (i = 0; gname[i] != '_'; i++) {
if (gname[i] == '\0')
return CONNMAN_SERVICE_TYPE_UNKNOWN;
if (i >= sizeof(type)-1) /* -1 for \0 */
return CONNMAN_SERVICE_TYPE_UNKNOWN;
type[i] = gname[i];
}
type[i] = '\0';
return __connman_service_string2type(type);
}
static void __profile_entry_list(DBusMessageIter *iter, void *arg)
{
struct connman_profile *profile = arg;
GKeyFile *keyfile;
gchar **groups;
int i;
_DBG_PROFILE("profile %s", __profile_name(profile));
keyfile = __connman_storage_open(&profile->ident);
if (keyfile == NULL)
return;
groups = g_key_file_get_groups(keyfile, NULL);
for (i = 0; groups[i] != NULL; i++) {
const char *gname = groups[i];
switch (get_service_type(gname)) {
case CONNMAN_SERVICE_TYPE_UNKNOWN:
case CONNMAN_SERVICE_TYPE_ETHERNET:
break;
case CONNMAN_SERVICE_TYPE_BLUETOOTH:
case CONNMAN_SERVICE_TYPE_WIMAX:
/* NB: don't care about these right now */
break;
case CONNMAN_SERVICE_TYPE_CELLULAR:
case CONNMAN_SERVICE_TYPE_WIFI:
case CONNMAN_SERVICE_TYPE_VPN:
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
&gname);
break;
}
}
g_strfreev(groups);
__connman_storage_close(&profile->ident, keyfile, FALSE);
}
static DBusMessage *get_properties(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct connman_profile *profile = data;
DBusMessage *reply;
DBusMessageIter array, dict;
_DBG_PROFILE("conn %p", conn);
reply = dbus_message_new_method_return(msg);
if (reply == NULL)
return NULL;
dbus_message_iter_init_append(reply, &array);
dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
if (profile->name != NULL)
connman_dbus_dict_append_variant(&dict, "Name",
DBUS_TYPE_STRING, &profile->name);
if (profile == global_profile()) {
gchar *str;
connman_dbus_dict_append_variant(&dict, "OfflineMode",
DBUS_TYPE_BOOLEAN, &profile->offlinemode);
if (profile->country != NULL)
connman_dbus_dict_append_variant(&dict, "Country",
DBUS_TYPE_STRING, &profile->country);
str = __connman_service_mask_to_list(profile->checkportal);
if (str != NULL) {
connman_dbus_dict_append_variant(&dict,
"CheckPortalList", DBUS_TYPE_STRING, &str);
g_free(str);
}
if (profile->portal_url != NULL)
connman_dbus_dict_append_variant(&dict, "PortalURL",
DBUS_TYPE_STRING, &profile->portal_url);
connman_dbus_dict_append_variant(&dict, "ArpGateway",
DBUS_TYPE_BOOLEAN, &profile->arpgateway);
}
if (profile == active_profile())
connman_dbus_dict_append_variant_array(&dict, "Services",
DBUS_TYPE_OBJECT_PATH, __connman_service_list, NULL);
connman_dbus_dict_append_variant_array(&dict, "Entries",
DBUS_TYPE_STRING, __profile_entry_list, profile);
dbus_message_iter_close_container(&array, &dict);
return reply;
}
static DBusMessage *set_property(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct connman_profile *profile = data;
DBusMessageIter iter, value;
const char *name;
int type;
_DBG_PROFILE("conn %p", conn);
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &name);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &value);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
type = dbus_message_iter_get_arg_type(&value);
if (g_str_equal(name, "Name") == TRUE) {
const char *name;
if (type != DBUS_TYPE_STRING)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&value, &name);
g_free(profile->name);
profile->name = g_strdup(name);
if (profile->name != NULL) {
connman_dbus_send_property_changed_variant(
profile->path, CONNMAN_PROFILE_INTERFACE, "Name",
DBUS_TYPE_STRING, &profile->name);
}
__connman_storage_save_profile(profile);
} else
return __connman_error_invalid_property(msg);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static void __append_str(DBusMessageIter *dict, const char *name,
GKeyFile *keyfile, const char *ident)
{
char *val = g_key_file_get_string(keyfile, ident, name, NULL);
if (val != NULL) {
connman_dbus_dict_append_variant(dict, name,
DBUS_TYPE_STRING, &val);
g_free(val);
}
}
static void __append_bool(DBusMessageIter *dict, const char *name,
GKeyFile *keyfile, const char *ident)
{
char *val = g_key_file_get_string(keyfile, ident, name, NULL);
if (val != NULL) {
connman_bool_t b = (g_strcmp0(val, "true") == 0 ? TRUE : FALSE);
connman_dbus_dict_append_variant(dict, name,
DBUS_TYPE_BOOLEAN, &b);
g_free(val);
}
}
static DBusMessage *get_entry(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct connman_profile *profile = data;
GKeyFile *keyfile;
const char *ident, *val;
gchar **tokens;
DBusMessageIter iter, array, dict;
DBusMessage *reply;
int len;
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &ident);
keyfile = __connman_storage_open(&profile->ident);
if (keyfile == NULL) {
_DBG_PROFILE("cannot open keyfile %s", __profile_name(profile));
return __connman_error_not_found(msg); /* XXX */
}
if (g_key_file_has_group(keyfile, ident) == FALSE) {
_DBG_PROFILE("keyfile %s ident %s not found",
__profile_name(profile), ident);
__connman_storage_close(&profile->ident, keyfile, FALSE);
return __connman_error_not_found(msg);
}
_DBG_PROFILE("profile %s ident %s", __profile_name(profile), ident);
/*
* Split group name into components; e.g.
* <type>_<device>_<ssid>_<mode>_<security> for wifi
*/
tokens = g_strsplit(ident, "_", 0);
len = g_strv_length(tokens);
if (len < 2) {
_DBG_PROFILE("profile %s ident %s malformed, len %d",
__profile_name(profile), ident, len);
g_strfreev(tokens);
__connman_storage_close(&profile->ident, keyfile, FALSE);
return __connman_error_invalid_arguments(msg);
}
reply = dbus_message_new_method_return(msg);
if (reply == NULL) {
_DBG_PROFILE("cannot allocate reply");
g_strfreev(tokens);
__connman_storage_close(&profile->ident, keyfile, FALSE);
return NULL;
}
dbus_message_iter_init_append(reply, &array);
dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
val = tokens[0];
connman_dbus_dict_append_variant(&dict, "Type", DBUS_TYPE_STRING, &val);
__append_str(&dict, "Name", keyfile, ident);
__append_bool(&dict, "AutoConnect", keyfile, ident);
__append_str(&dict, "Failure", keyfile, ident);
__append_str(&dict, "Modified", keyfile, ident);
__append_str(&dict, "Passphrase", keyfile, ident);
__append_str(&dict, "UIData", keyfile, ident);
__append_str(&dict, "GUID", keyfile, ident);
switch (__connman_service_string2type(tokens[0])) {
case CONNMAN_SERVICE_TYPE_WIFI:
if (len != 5 && len != 6) {
_DBG_PROFILE("profile %s ident %s bad token cnt %d",
__profile_name(profile), ident, len);
break;
}
val = tokens[3];
connman_dbus_dict_append_variant(&dict, "Mode",
DBUS_TYPE_STRING, &val);
val = tokens[4];
/* NB: g_strsplit breaks 802_1x into 802+1x; restore */
if (g_strcmp0(val, "802") == 0)
val = "802_1x";
connman_dbus_dict_append_variant(&dict, "Security",
DBUS_TYPE_STRING, &val);
__append_bool(&dict, "WiFi.HiddenSSID", keyfile, ident);
break;
case CONNMAN_SERVICE_TYPE_VPN: {
struct __connman_provider_append_profile_properties_args args;
args.keyfile = keyfile;
args.ident = ident;
connman_dbus_dict_append_dict(&dict, "Provider",
__connman_provider_append_profile_properties, &args);
break;
}
case CONNMAN_SERVICE_TYPE_CELLULAR:
/* TODO(sleffler) extract cellular attributes */
break;
default:
break;
}
dbus_message_iter_close_container(&array, &dict);
g_strfreev(tokens);
__connman_storage_close(&profile->ident, keyfile, FALSE);
return reply;
}
static DBusMessage *delete_entry(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct connman_profile *profile = data;
DBusMessageIter iter;
const char *identifier;
struct connman_service *service;
int err;
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &identifier);
_DBG_PROFILE("profile %s:%s %s", profile->ident.user,
profile->ident.ident, identifier);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
service = __connman_service_lookup(identifier);
if (service != NULL) {
__connman_service_disconnect(service);
/* NB: this does not remove the service */
__connman_service_reset_in_memory(service);
__connman_service_set_profile(service, NULL);
}
/* Remove directly from profile */
err = __connman_profile_delete_entry(profile, identifier);
if (err)
return __connman_error_failed(msg, -err);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static GDBusMethodTable profile_methods[] = {
{ "GetProperties", "", "a{sv}", get_properties },
{ "SetProperty", "sv", "", set_property },
{ "GetEntry", "s", "a{sv}", get_entry },
{ "DeleteEntry", "s", "", delete_entry },
{ },
};
static GDBusSignalTable profile_signals[] = {
{ "PropertyChanged", "sv" },
{ },
};
static void free_ident(struct connman_storage_ident *ident)
{
/* NB: blech, don't reclaim, it's statically allocated */
if (!ident_equal(ident, &default_ident)) {
g_free(ident->user);
g_free(ident->ident);
}
}
static void free_profile(struct connman_profile *profile)
{
free_ident(&profile->ident);
g_free(profile->path);
g_free(profile->name);
g_free(profile);
}
static void unregister_profile(gpointer data)
{
struct connman_profile *profile = data;
connman_info("Remove profile %s:%s", profile->ident.user,
profile->ident.ident);
g_dbus_unregister_interface(connection, profile->path,
CONNMAN_PROFILE_INTERFACE);
clear_timeout(profile);
if (profile == default_profile)
default_profile = NULL;
free_profile(profile);
}
static char *getpath(const struct connman_storage_ident *ident)
{
if (ident->user != NULL) {
/* NB: check for two tokens done in validate_ident */
return g_strdup_printf("/profile/%s/%s",
ident->user, ident->ident);
} else
return g_strdup_printf("/profile/%s", ident->ident);
}
/*
* Create an in-memory profile using the specified identifier
* and name. The object path and a reference to the profile
* data structure are returned on success.
*/
static int create_profile(struct connman_storage_ident *ident,
const char *name, const char **path, struct connman_profile **pprofile)
{
struct connman_profile *profile;
_DBG_PROFILE("ident %s:%s name %s", ident->user, ident->ident, name);
profile = g_try_new0(struct connman_profile, 1);
if (profile == NULL) {
free_ident(ident);
return -ENOMEM;
}
profile->ident = *ident;
profile->path = getpath(ident);
/* TODO(sleffler) check ident.user */
if (profile->ident.ident == NULL || profile->path == NULL) {
free_profile(profile);
return -ENOMEM;
}
if (g_hash_table_lookup(profile_hash, profile->path) != NULL) {
free_profile(profile);
return -EEXIST;
}
profile->checkportal = profile_default_checkportal_mask;
profile->name = g_strdup(name);
__connman_storage_load_profile(profile);
g_hash_table_insert(profile_hash, g_strdup(profile->path), profile);
connman_info("Add profile %s:%s", ident->user, ident->ident);
if (ident_equal(ident, &default_ident))
default_profile = profile;
g_dbus_register_interface(connection, profile->path,
CONNMAN_PROFILE_INTERFACE,
profile_methods, profile_signals,
NULL, profile, NULL);
if (path != NULL)
*path = profile->path;
_DBG_PROFILE("profile %p path %s", profile, profile->path);
*pprofile = profile;
return 0;
}
/*
* Check a profile identifier token. This must be non-null and
* made up of alpha-numeric chars suitable for use in a D-bus
* object path.
*/
static gboolean validate_token(const char *ident)
{
unsigned int i;
unsigned int len = strlen(ident);
if (len < 1)
return FALSE;
for (i = 0; i < len; i++) {
if (ident[i] >= '0' && ident[i] <= '9')
continue;
if (ident[i] >= 'a' && ident[i] <= 'z')
continue;
if (ident[i] >= 'A' && ident[i] <= 'Z')
continue;
return FALSE;
}
return TRUE;
}
/*
* Validate the profile identifier. It must be suitable
* for use in a D-Bus object path and, optionally, be of
* the form ~user/ident to signify a per-user profile.
*/
static gboolean parse_ident(const char *str,
struct connman_storage_ident *ident)
{
gboolean is_valid;
if (str[0] == '~') {
/* syntax is ~user/name for profile in cryptohome */
gchar **tokens = g_strsplit_set(str, "~/", 4);
if (g_strv_length(tokens) == 3) {
is_valid = (validate_token(tokens[1]) == TRUE &&
validate_token(tokens[2]) == TRUE);
if (is_valid) {
ident->user = g_strdup(tokens[1]);
ident->ident = g_strdup(tokens[2]);
}
} else
is_valid = FALSE;
g_strfreev(tokens);
} else {
is_valid = validate_token(str);
if (is_valid) {
ident->user = NULL;
ident->ident = g_strdup(str);
}
}
return is_valid;
}
/*
* Lookup a profile on the stack by object path.
*/
static struct connman_profile *lookup_stack_by_path(const char *path)
{
int i;
for (i = cur_profile; i >= 0; i--)
if (g_strcmp0(profile_stack[i]->path, path) == 0)
return profile_stack[i];
return NULL;
}
/*
* Push a profile on the stack and make it the ``active profile''.
* The profile may be currently registered in memory or previously
* created by CreateProfile. The profile may not already be on
* the stack.
*/
int __connman_profile_push(const char *ident, const char *name,
const char **path)
{
struct connman_profile *profile;
struct connman_storage_ident sid;
char *tmp_path;
int err;
_DBG_PROFILE("ident %s name %s", ident, name);
if (parse_ident(ident, &sid) == FALSE) {
connman_error("%s: invalid profile name %s", __func__, ident);
return -EINVAL;
}
if (cur_profile+1 >= PROFILE_MAX) {
connman_error("%s: too many profiles (max %d)", __func__,
PROFILE_MAX);
free_ident(&sid);
return -EMFILE;
}
/*
* Check for in-memory profile by way of a CreateProfile request.
*/
tmp_path = getpath(&sid);
if (tmp_path == NULL) {
connman_error("%s: no memory for %s", __func__, ident);
free_ident(&sid);
return -ENOMEM;
}
profile = g_hash_table_lookup(profile_hash, tmp_path);
g_free(tmp_path);
if (profile == NULL) {
/*
* Not in memory; accept an existing file.
*/
if (__connman_storage_exists(&sid) == FALSE) {
connman_error("%s: profile %s does not exist",
__func__, ident);
free_ident(&sid);
return -ENOENT;
}
err = create_profile(&sid, name, path, &profile);
if (err < 0) {
connman_error("%s: cannot open, error %d",
__func__, err);
/* NB: create_profile reclaims sid */
return err;
}
} else {
free_ident(&sid); /* NB: not needed below */
/*
* Check this profile is not already on the stack.
*/
if (lookup_stack_by_path(profile->path) != NULL) {
connman_error("%s: already pushed", __func__);
return -EEXIST;
}
if (path != NULL)
*path = profile->path;
}
profile_stack[++cur_profile] = profile;
profiles_changed();
__connman_notifier_profile_push(profile);
return 0;
}
/*
* Pop the profile from the top of the stack and remove it from
* the in-memory table. Any associated services are invalidated
* (credentials revoked and connections closed). After a pop we
* generate a PropertyChanged signal for Manager.Profiles.
*
* An optional identifier may be specified. If specified it is
* checked against the active profile and if not the same then
* the request is rejected. This is useful to ensure the right
* profile is pop'd (as might happen on logout if an associated
* push failed for some reason).
*/
int __connman_profile_pop(const char *ident)
{
struct connman_profile *profile;
_DBG_PROFILE("ident %s", ident);
if (cur_profile < 0) {
connman_error("%s: profile stack empty", __func__);
return -EINVAL;
}
profile = profile_stack[cur_profile];
if (ident != NULL) {
struct connman_storage_ident sid;
if (parse_ident(ident, &sid) == FALSE) {
connman_error("%s: invalid profile name %s",
__func__, ident);
return -EINVAL;
}
if (ident_equal(&profile->ident, &sid) == FALSE) {
connman_error("%s: %s is not the active profile",
__func__, ident);
free_ident(&sid);
return -ENXIO;
}
free_ident(&sid);
}
/*
* Pop the stack, invalidate all objects holding references
* to the profile, then remove it from the in-memory table.
* We do the reclaim after notification in case anyone holding
* a reference tries to use it.
*/
cur_profile--;
__connman_notifier_profile_pop(profile);
g_hash_table_remove(profile_hash, profile->path);
profiles_changed();
return 0;
}
/*
* Create a profile file and register it in memory. The
* file is created with minimal contents.
* TODO(sleffler) disallow overwriting an existing file?
*/
int __connman_profile_create(const char *name, const char **path)
{
struct connman_profile *profile;
struct connman_storage_ident sid;
int err;
_DBG_PROFILE("name %s", name);
if (parse_ident(name, &sid) == FALSE) {
connman_error("%s: invalid profile name %s)", __func__, name);
return -EINVAL;
}
err = create_profile(&sid, NULL, path, &profile);
if (err < 0) {
connman_error("%s: cannot open, error %d", __func__, err);
/* NB: create_profile reclaims sid */
return err;
}
__connman_storage_save_profile(profile);
profiles_changed();
return 0;
}
/*
* Delete a profile and remove from memory. The default
* profile may not be removed/deleted. Likewise, one
* cannot remove a profile pushed on the stack.
*/
int __connman_profile_remove(const char *ident)
{
struct connman_profile *profile;
struct connman_storage_ident sid;
char *tmp_path;
int err = 0;
_DBG_PROFILE("ident %s", ident);
if (parse_ident(ident, &sid) == FALSE) {
connman_error("%s: invalid profile name %s)", __func__, ident);
return -EINVAL;
}
if (ident_equal(&sid, &default_ident)) {
/* TODO(sleffler) should this be permitted? */
connman_error("%s: cannot delete default profile", __func__);
err = -EINVAL;
goto done;
}
/*
* Check for in-memory profile by way of a CreateProfile request.
*/
tmp_path = getpath(&sid);
if (tmp_path == NULL) {
connman_error("%s: no memory for %s", __func__, ident);
err = -ENOMEM;
goto done;
}
profile = g_hash_table_lookup(profile_hash, tmp_path);
g_free(tmp_path);
if (profile != NULL) {
/*
* Check this profile is not on the stack.
*/
if (lookup_stack_by_path(profile->path) != NULL) {
connman_error("%s: cannot delete (on the stack)",
__func__);
err = -EEXIST;
goto done;
}
g_hash_table_remove(profile_hash, profile->path);
profiles_changed();
}
__connman_storage_delete(&sid);
done:
free_ident(&sid);
return err;
}
/*
* Initialize the profile stack by pushing the default
* (global) profile. Unlike connman we do not read in
* all available .profile's in the global dir.
*/
static int profile_init(void)
{
struct connman_profile *profile;
int err;
err = create_profile(&default_ident, "Default", NULL, &profile);
if (err == 0)
profile_stack[++cur_profile] = profile;
return err;
}
static int profile_load(struct connman_profile *profile)
{
GKeyFile *keyfile;
GError *error = NULL;
connman_bool_t offlinemode;
connman_bool_t arpgateway;
char *name, *country, *str;
_DBG_PROFILE("profile %s", __profile_name(profile));
keyfile = __connman_storage_open(&profile->ident);
if (keyfile == NULL)
return -EIO;
name = g_key_file_get_string(keyfile, "global", "Name", NULL);
if (name != NULL) {
g_free(profile->name);
profile->name = name;
}
country = g_key_file_get_string(keyfile, "global", "Country", NULL);
if (country != NULL) {
g_free(profile->country);
profile->country = country;
}
str = g_key_file_get_string(keyfile, "global", "CheckPortalList", NULL);
if (str != NULL) {
profile->checkportal = __connman_service_list_to_mask(str);
g_free(str);
} else
profile->checkportal = profile_default_checkportal_mask;
str = g_key_file_get_string(keyfile, "global", "PortalURL", NULL);
if (str != NULL) {
g_free(profile->portal_url);
profile->portal_url = str;
}
offlinemode = g_key_file_get_boolean(keyfile, "global",
"OfflineMode", &error);
if (error == NULL)
profile->offlinemode = offlinemode;
g_clear_error(&error);
arpgateway = g_key_file_get_boolean(keyfile, "global",
"ArpGateway", &error);
if (error == NULL)
profile->arpgateway = arpgateway;
else
profile->arpgateway = TRUE;
g_clear_error(&error);
__connman_storage_close(&profile->ident, keyfile, FALSE);
return 0;
}
static int profile_save(struct connman_profile *profile)
{
GKeyFile *keyfile;
gchar *str;
_DBG_PROFILE("profile %s", __profile_name(profile));
keyfile = __connman_storage_open(&profile->ident);
if (keyfile == NULL)
return -EIO;
if (profile->name != NULL)
g_key_file_set_string(keyfile, "global",
"Name", profile->name);
if (profile->country != NULL)
g_key_file_set_string(keyfile, "global",
"Country", profile->country);
str = (profile->checkportal != profile_default_checkportal_mask)?
__connman_service_mask_to_list(profile->checkportal):NULL;
if (str != NULL) {
g_key_file_set_string(keyfile, "global",
"CheckPortalList", str);
g_free(str);
}
if (profile->portal_url != NULL)
g_key_file_set_string(keyfile, "global",
"PortalURL", profile->portal_url);
g_key_file_set_boolean(keyfile, "global",
"OfflineMode", profile->offlinemode);
g_key_file_set_boolean(keyfile, "global",
"ArpGateway", profile->arpgateway);
__connman_storage_close(&profile->ident, keyfile, TRUE);
return 0;
}
static struct connman_storage profile_storage = {
.name = "profile",
.priority = CONNMAN_STORAGE_PRIORITY_LOW,
.profile_init = profile_init,
.profile_load = profile_load,
.profile_save = profile_save,
};
int __connman_profile_init(void)
{
const char *portal_list;
_DBG_PROFILE("");
connection = connman_dbus_get_connection();
if (connection == NULL)
return -1;
if (connman_storage_register(&profile_storage) < 0)
connman_error("Failed to register profile storage");
profile_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, unregister_profile);
portal_list = connman_option_get_string("portal-list");
if (portal_list != NULL)
profile_default_checkportal_mask =
__connman_service_list_to_mask(portal_list);
return 0;
}
int __connman_profile_push_batch(char **profiles)
{
int i;
if (profiles == NULL)
return 0;
for (i = 0; profiles[i] != NULL; i++) {
int err = __connman_profile_push(profiles[i], NULL, NULL);
if (err != 0)
return err;
}
return 0;
}
void __connman_profile_cleanup(void)
{
_DBG_PROFILE("");
if (connection == NULL)
return;
while (cur_profile >= 0)
__connman_profile_pop(NULL);
clear_timeout(NULL); /* NB: clear global timer */
g_hash_table_destroy(profile_hash);
profile_hash = NULL;
connman_storage_unregister(&profile_storage);
dbus_connection_unref(connection);
}