blob: 85d3f8148e3902fb61547cc9f7407cf9f7e8d3f0 [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 <ctype.h>
#include <stdio.h>
#include <string.h>
#include <gdbus.h>
#include <connman/assert.h>
#include <connman/blob.h>
#include <connman/wifi.h>
#include "connman.h"
#define _DBG_SERVICE(fmt, arg...) DBG(DBG_SERVICE, fmt, ## arg)
#define MAX_CONNECT_RETRIES 5
#define CONNMAN_SERVICE_NOTIFY_SLOW 1500 /* ms */
#define CONNMAN_SERVICE_NOTIFY_FAST 125 /* ms */
#define CONNECT_TIMEOUT_SECS (45)
#define PORTAL_RECHECK_SECS 30
/*
* Service priority is used to order services. Priority is
* optionally specified by clients in the range [1..100].
* By default a service has no assigned priority. Services
* with a priority always sort before services w/o a priority.
*/
#define CONNMAN_SERVICE_PRI_MIN 1
#define CONNMAN_SERVICE_PRI_MAX 100
#define CONNMAN_SERVICE_PRI_NONE 0
static DBusConnection *connection = NULL;
static GSequence *service_list = NULL;
static GHashTable *service_hash = NULL;
enum connman_service_check_portal {
CONNMAN_SERVICE_CHECK_PORTAL_FALSE = 0,
CONNMAN_SERVICE_CHECK_PORTAL_TRUE = 1,
CONNMAN_SERVICE_CHECK_PORTAL_AUTO = 2,
};
struct online_portal_data {
char *url; /* URL for online portal */
char *method; /* HTTP method to access portal */
char *postdata; /* encoded data if method is POST */
};
/*
* ID of timeout event for rechecking the portal
*/
static guint portal_recheck_timeout = 0;
struct connman_service {
gint refcount;
char *identifier;
char *path;
char *guid;
char *uidata;
struct connman_profile *profile;
enum connman_service_type type;
enum connman_service_mode mode;
enum connman_service_security security;
enum connman_service_state state;
enum connman_service_error error;
enum connman_service_check_portal check_portal;
connman_uint8_t pri;
connman_uint8_t strength;
connman_bool_t favorite;
connman_bool_t previously_connected;
connman_bool_t hidden;
connman_bool_t ignore;
connman_bool_t autoconnect;
connman_bool_t userconnect;
connman_bool_t active;
connman_bool_t save_credentials;
connman_bool_t save_to_profile;
guint writeback_task;
GTimeVal modified;
GTimeVal last_attempt; /* last connection attempt */
char *name;
char *passphrase;
struct {
char *identity;
char *eap;
char *inner_eap;
char *anonymous_identity;
char *client_cert; /* formerly certpath */
char *cert_id;
char *private_key;
char *private_key_passwd;
char *key_id;
char *ca_cert; /* formerly authpath */
char *ca_cert_id;
char *ca_cert_nss;
connman_bool_t use_system_cas;
char *pin;
char *password;
char *key_mgmt;
} eap;
char *proxy_config;
/* TODO(sleffler) overlay storage */
struct {
struct online_portal_data olp;
char *usage_url;
enum connman_network_activation_state activation_state;
enum connman_network_cellular_technology network_technology;
enum connman_network_cellular_roaming_state roaming_state;
struct connman_network_apn apn;
struct connman_network_apn last_good_apn;
struct connman_network_operator serving_operator;
} cellular;
struct {
unsigned int wep_key_len;
connman_uint8_t wep_key_idx;
connman_uint8_t wep_key_matter[MAX_WEP_KEYSPACE];
connman_bool_t hidden_ssid;
connman_bool_t cached_hex_ssid;
char *hex_ssid;
} wifi;
struct connman_device *device;
struct connman_network *network;
struct connman_provider *provider;
struct sticky_route *sticky_route;
guint timeout;
int scangen;
GTimeVal last_state[CONNMAN_SERVICE_STATE_MAX];
/* we have indicated externally that this service is in the watchlist */
connman_bool_t in_watchlist;
};
struct sticky_route {
char *dest;
uint32_t index;
in_addr_t ip_addr;
in_addr_t gw_addr;
struct connman_rtnl rtnl;
struct connman_service *service;
};
/*
* This structure holds global state about "watchlist" services. It
* holds the g_timeout handle, and the state information we use (in
* addition to the "in_watchlist" struct connman_service member
* above) to iterate over services to determine whether the watchlist
* has changed since we last sent out a notification.
*/
struct service_watchlist_state {
guint notify_timeout; /* timeout handle for watchlist notification */
int count; /* number of watchlist services globally */
int last_count;
connman_bool_t do_notify; /* whether we should notify on this round */
};
static struct service_watchlist_state svc_watchlist;
static int is_suspended = FALSE;
static int is_shutting_down = FALSE;
/*
* Devices waiting for autoconnection. We hold a ref while they're on
* here.
*/
static GQueue *auto_connect_queue = NULL;
/*
* Service priorities: this defines an ordering for prioritizing
* services to clients and in choosing the service through which
* we route traffic (the default route). Lower values are better.
* If services are assigned the same priority we fall back to
* other attributes (e.g. signal strength) to prioritize.
*/
static int service_priority[CONNMAN_SERVICE_TYPE_MAX];
/* TODO(sleffler): don't agree BT > WiFi etc. */
#define CONNMAN_SERVICE_PRIO_DEFAULT "vpn,ethernet,bluetooth,wifi,wimax,cellular"
/*
* Security priorities: this defines an ordering for prioritizing
* services to clients and in choosing the service through which
* we route traffic (the default route). Lower values are better.
* If securities are assigned the same priority we fall back to
* other attributes (e.g. signal strength) to prioritize.
*/
static int security_priority[CONNMAN_SERVICE_SECURITY_MAX] = {
[CONNMAN_SERVICE_SECURITY_UNKNOWN] = 3,
[CONNMAN_SERVICE_SECURITY_NONE] = 3,
[CONNMAN_SERVICE_SECURITY_WEP] = 2,
[CONNMAN_SERVICE_SECURITY_WPA] = 1,
[CONNMAN_SERVICE_SECURITY_RSN] = 1,
[CONNMAN_SERVICE_SECURITY_802_1X] = 0,
[CONNMAN_SERVICE_SECURITY_PSK] = 1,
};
static const char *__statename(enum connman_service_state state)
{
static const char *statenames[] = {
[CONNMAN_SERVICE_STATE_UNKNOWN] = "UNKNOWN",
[CONNMAN_SERVICE_STATE_IDLE] = "IDLE",
[CONNMAN_SERVICE_STATE_CARRIER] = "CARRIER",
[CONNMAN_SERVICE_STATE_ASSOCIATION] = "ASSOCIATION",
[CONNMAN_SERVICE_STATE_CONFIGURATION] = "CONFIGURATION",
[CONNMAN_SERVICE_STATE_READY] = "READY",
[CONNMAN_SERVICE_STATE_PORTAL] = "PORTAL",
[CONNMAN_SERVICE_STATE_ONLINE] = "ONLINE",
[CONNMAN_SERVICE_STATE_DISCONNECT] = "DISCONNECT",
[CONNMAN_SERVICE_STATE_FAILURE] = "FAILURE",
[CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE] = "ACTIVATION-FAILURE",
};
return statenames[state];
}
static const char* kEAPIdentity = "EAP.Identity";
static const char* kEAPEAP = "EAP.EAP";
static const char* kEAPInnerEAP = "EAP.InnerEAP";
static const char* kEAPAnonymousIdentity = "EAP.AnonymousIdentity";
static const char* kEAPClientCert = "EAP.ClientCert";
static const char* kEAPCertID = "EAP.CertID";
static const char* kEAPPrivateKey = "EAP.PrivateKey";
static const char* kEAPPrivateKeyPassword = "EAP.PrivateKeyPassword";
static const char* kEAPKeyID = "EAP.KeyID";
static const char* kEAPCACert = "EAP.CACert";
static const char* kEAPCACertID = "EAP.CACertID";
static const char* kEAPCACertNSS = "EAP.CACertNSS";
static const char* kEAPUseSystemCAs = "EAP.UseSystemCAs";
static const char* kEAPPIN = "EAP.PIN";
static const char* kEAPPassword = "EAP.Password";
static const char* kEAPKeyMgmt = "EAP.KeyMgmt";
static void profile_changed(struct connman_service *service);
static void service_writeback_cancel(struct connman_service *service);
static void service_modified(struct connman_service *service);
static void __service_resort(struct connman_service *service);
static connman_bool_t is_connected(const struct connman_service *service);
static DBusMessage *__service_set_property(struct connman_service *service,
DBusMessageIter *entry, DBusMessage *msg);
static struct connman_service *__connman_service_get(const char *identifier,
connman_bool_t *created_out);
static enum connman_service_mode convert_wifi_mode(const char *mode);
static enum connman_service_mode convert_wifi_security(const char *security);
static connman_bool_t is_timeset(const GTimeVal *tv)
{
return !(tv->tv_sec == 0 && tv->tv_usec == 0);
}
static void append_path(gpointer value, gpointer user_data)
{
struct connman_service *service = value;
DBusMessageIter *iter = user_data;
if (service->path == NULL || service->hidden == TRUE)
return;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
&service->path);
}
void __connman_service_list(DBusMessageIter *iter, void *arg)
{
_DBG_SERVICE("");
if (service_list != NULL)
g_sequence_foreach(service_list, append_path, iter);
}
static inline connman_bool_t is_watchlist_state(
enum connman_service_state state)
{
return (state > CONNMAN_SERVICE_STATE_IDLE);
}
/*
* Iterate over the service list and see:
* (a) If there are any services whose current state differs from the last
* state we advertised.
* (b) Keep a count of the total number of watchlist services we see so we
* can compare it to our last count.
*/
static void assess_watchlist_services(gpointer value, gpointer user_data)
{
struct connman_service *service = value;
struct service_watchlist_state *state = user_data;
if (service->path == NULL || service->hidden == TRUE)
return;
if (is_watchlist_state(service->state)) {
state->count++;
if (service->in_watchlist == FALSE) {
state->do_notify = TRUE;
service->in_watchlist = TRUE;
}
} else {
if (service->in_watchlist == TRUE) {
state->do_notify = TRUE;
service->in_watchlist = FALSE;
}
}
}
static void append_watchlist_path(gpointer value, gpointer user_data)
{
struct connman_service *service = value;
DBusMessageIter *iter = user_data;
if (service->path == NULL || service->hidden == TRUE ||
is_watchlist_state(service->state) == FALSE)
return;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
&service->path);
}
void __connman_service_watchlist(DBusMessageIter *iter, void *arg)
{
_DBG_SERVICE("");
g_sequence_foreach(service_list, append_watchlist_path, iter);
}
static gboolean notify_watchlist(gpointer user_data)
{
svc_watchlist.notify_timeout = 0;
svc_watchlist.do_notify = FALSE;
svc_watchlist.count = 0;
g_sequence_foreach(service_list, assess_watchlist_services,
&svc_watchlist);
if (svc_watchlist.last_count != svc_watchlist.count ||
svc_watchlist.do_notify == TRUE) {
_DBG_SERVICE("Sending update because svc_count change is "
"%d -> %d and do_notify is %d",
svc_watchlist.last_count, svc_watchlist.count,
svc_watchlist.do_notify);
svc_watchlist.last_count = svc_watchlist.count;
connman_dbus_send_property_changed_array(
CONNMAN_MANAGER_PATH, CONNMAN_MANAGER_INTERFACE,
"ServiceWatchList", DBUS_TYPE_OBJECT_PATH,
__connman_service_watchlist, NULL);
}
return FALSE;
}
/*
* Outgoing watchlist notifications are batched up by actually calling
* the notification in a timeout function.
*/
void connman_service_notify_watchlist(enum connman_service_state state)
{
_DBG_SERVICE("");
if (is_watchlist_state(state)) {
/*
* Notification of a service leaving the idle state
* is sent quickly, overriding any previous timeout
* (but necessarily pushing out any pending
* notification further into the future).
*/
if (svc_watchlist.notify_timeout > 0)
g_source_remove(svc_watchlist.notify_timeout);
svc_watchlist.notify_timeout =
g_timeout_add(CONNMAN_SERVICE_NOTIFY_FAST,
notify_watchlist, NULL);
_DBG_SERVICE("scheduled fast watchlist service notify");
} else {
/*
* Notification of services entering an idle state are
* pushed out with a slow timeout, but any existing timer
* is left intact -- so it doesn't delay any more urgent
* prior notification.
*/
if (svc_watchlist.notify_timeout == 0) {
svc_watchlist.notify_timeout =
g_timeout_add(CONNMAN_SERVICE_NOTIFY_SLOW,
notify_watchlist, NULL);
_DBG_SERVICE("scheduled slow watchlist service notify");
}
}
}
struct find_data {
const char *path;
struct connman_service *service;
};
static void compare_path(gpointer value, gpointer user_data)
{
struct connman_service *service = value;
struct find_data *data = user_data;
if (data->service != NULL)
return;
if (g_strcmp0(service->path, data->path) == 0)
data->service = service;
}
static struct connman_service *find_service(const char *path)
{
struct find_data data = { .path = path, .service = NULL };
_DBG_SERVICE("path %s", path);
g_sequence_foreach(service_list, compare_path, &data);
return data.service;
}
struct connman_profile *__connman_service_get_profile(
struct connman_service *service)
{
return service->profile;
}
/*
* Set the service's bound profile.
*/
void __connman_service_set_profile(struct connman_service *service,
struct connman_profile *profile)
{
_DBG_SERVICE("profile %p -> %p", service->profile, profile);
service->profile = profile;
if (profile == NULL)
service_writeback_cancel(service);
}
/*
* Handle a profile being pop'd off the stack (e.g. on logout).
* Disconnect and invalidate all services bound to the specified
* profile. Note that services are not always reclaimed (doing
* so is hard given the way refcount'ing works) so we inhibit
* saving to the profile while we scrub the in-memory state.
*/
static void invalidate_profile(gpointer value, gpointer user_data)
{
struct connman_service *service = value;
const struct connman_profile *profile = user_data;
if (service->profile == profile) {
connman_bool_t old_save;
_DBG_SERVICE("service %s refcount %d",
service->identifier, service->refcount);
service->profile = NULL;
if (service->type == CONNMAN_SERVICE_TYPE_ETHERNET) {
/* TODO(sleffler) really anything created by device */
connman_error("%s: skip ethernet device", __func__);
return;
}
connman_service_ref(service);
old_save = service->save_to_profile;
service->save_to_profile = FALSE; /* inhibit saving */
__connman_service_disconnect(service);
__connman_service_reset_in_memory(service);
if (service->provider != NULL) {
/*
* Drop the reference created by Manager.GetService
* so the service goes away below on last unref.
*/
connman_service_unref(service);
}
service->save_to_profile = old_save; /* restore saving */
__connman_profile_load_service(service);
connman_service_unref(service);
}
}
static void service_profile_pop(struct connman_profile *profile)
{
_DBG_SERVICE("profile %p", profile);
g_sequence_foreach(service_list, invalidate_profile, (gpointer)profile);
/* NB: no need to kick auto-connect; it will happen due to invalidate */
}
/*
* Handle a profile being push'd on the stack (e.g. on login).
* Bind/rebind service objects and kick the autoconnect logic
* in case we can now establish connectivity.
*/
static void load_profile(gpointer value, gpointer user_data)
{
struct connman_service *service = value;
struct connman_profile *old_profile;
old_profile = service->profile;
service->profile = NULL;
if (__connman_profile_load_service(service) != 0) {
/* NB: no need to re-load, contents should be intact */
service->profile = old_profile;
} else if (old_profile != NULL) {
/* the profile bound to the service changed */
profile_changed(service);
}
}
static void service_profile_push(struct connman_profile *profile)
{
_DBG_SERVICE("profile %p", profile);
g_sequence_foreach(service_list, load_profile, NULL);
__connman_service_auto_connect_any();
}
static const char *identifier(struct connman_service *service)
{
return service ? service->identifier : "<nil>";
}
static const char *service_type2string(enum connman_service_type type)
{
switch (type) {
case CONNMAN_SERVICE_TYPE_UNKNOWN:
break;
case CONNMAN_SERVICE_TYPE_ETHERNET:
return "ethernet";
case CONNMAN_SERVICE_TYPE_WIFI:
return "wifi";
case CONNMAN_SERVICE_TYPE_WIMAX:
return "wimax";
case CONNMAN_SERVICE_TYPE_BLUETOOTH:
return "bluetooth";
case CONNMAN_SERVICE_TYPE_CELLULAR:
return "cellular";
case CONNMAN_SERVICE_TYPE_VPN:
return "vpn";
default:
break;
}
return NULL;
}
enum connman_service_type __connman_service_string2type(const char *str)
{
enum connman_service_type type = CONNMAN_SERVICE_TYPE_UNKNOWN;
if (g_strcmp0(str, "ethernet") == 0)
type = CONNMAN_SERVICE_TYPE_ETHERNET;
else if (g_strcmp0(str, "wifi") == 0)
type = CONNMAN_SERVICE_TYPE_WIFI;
else if (g_strcmp0(str, "wimax") == 0)
type = CONNMAN_SERVICE_TYPE_WIMAX;
else if (g_strcmp0(str, "bluetooth") == 0)
type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
else if (g_strcmp0(str, "cellular") == 0)
type = CONNMAN_SERVICE_TYPE_CELLULAR;
else if (g_strcmp0(str, "vpn") == 0)
type = CONNMAN_SERVICE_TYPE_VPN;
return type;
}
uint32_t __connman_service_list_to_mask(const char *list)
{
gchar **taglist = g_strsplit(list, ",", 0);
gchar **tagp, *tag;
uint32_t mask;
mask = 0;
for (tagp = taglist; (tag = *tagp) != NULL; tagp++)
mask |= 1 << __connman_service_string2type(tag);
return mask;
}
gchar *__connman_service_mask_to_list(uint32_t mask)
{
if (mask != 0) {
gchar *tagvec[CONNMAN_SERVICE_TYPE_MAX+1];
int i, n;
n = 0;
for (i = 0; i < CONNMAN_SERVICE_TYPE_MAX; i++)
if (mask & (1<<i)) {
const char *str = service_type2string(i);
if (str != NULL)
tagvec[n++] = (gchar *) str;
}
tagvec[n] = NULL;
return g_strjoinv(",", tagvec);
} else
return g_strdup("");
}
static const char *mode2string(enum connman_service_mode mode)
{
switch (mode) {
case CONNMAN_SERVICE_MODE_UNKNOWN:
break;
case CONNMAN_SERVICE_MODE_MANAGED:
return "managed";
case CONNMAN_SERVICE_MODE_ADHOC:
return "adhoc";
}
return NULL;
}
static const char *security2string(enum connman_service_security security)
{
switch (security) {
case CONNMAN_SERVICE_SECURITY_UNKNOWN:
break;
case CONNMAN_SERVICE_SECURITY_NONE:
return "none";
case CONNMAN_SERVICE_SECURITY_WEP:
return "wep";
case CONNMAN_SERVICE_SECURITY_WPA:
return "wpa";
case CONNMAN_SERVICE_SECURITY_RSN:
return "rsn";
case CONNMAN_SERVICE_SECURITY_802_1X:
return "802_1x";
case CONNMAN_SERVICE_SECURITY_PSK:
return "psk";
default:
break;
}
return NULL;
}
const char *connman_service_get_state(const struct connman_service *service)
{
switch (service->state) {
case CONNMAN_SERVICE_STATE_UNKNOWN:
break;
case CONNMAN_SERVICE_STATE_IDLE:
return "idle";
case CONNMAN_SERVICE_STATE_CARRIER:
return "carrier";
case CONNMAN_SERVICE_STATE_ASSOCIATION:
return "association";
case CONNMAN_SERVICE_STATE_CONFIGURATION:
return "configuration";
case CONNMAN_SERVICE_STATE_READY:
return "ready";
case CONNMAN_SERVICE_STATE_PORTAL:
return "portal";
case CONNMAN_SERVICE_STATE_ONLINE:
return "online";
case CONNMAN_SERVICE_STATE_DISCONNECT:
return "disconnect";
case CONNMAN_SERVICE_STATE_FAILURE:
return "failure";
case CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE:
return "activation-failure";
default:
break;
}
return NULL;
}
static const char *error2string(enum connman_service_error error)
{
switch (error) {
case CONNMAN_SERVICE_ERROR_NO_ERROR:
break;
case CONNMAN_SERVICE_ERROR_OUT_OF_RANGE:
return "out-of-range";
case CONNMAN_SERVICE_ERROR_PIN_MISSING:
return "pin-missing";
case CONNMAN_SERVICE_ERROR_DHCP_FAILED:
return "dhcp-failed";
case CONNMAN_SERVICE_ERROR_CONNECT_FAILED:
return "connect-failed";
case CONNMAN_SERVICE_ERROR_BAD_PASSPHRASE:
return "bad-passphrase";
case CONNMAN_SERVICE_ERROR_BAD_WEPKEY:
return "bad-wepkey";
case CONNMAN_SERVICE_ERROR_ACTIVATION_FAILED:
return "activation-failed";
case CONNMAN_SERVICE_ERROR_NEED_EVDO:
return "need-evdo";
case CONNMAN_SERVICE_ERROR_NEED_HOME_NETWORK:
return "need-home-network";
case CONNMAN_SERVICE_ERROR_OTASP_FAILED:
return "otasp-failed";
case CONNMAN_SERVICE_ERROR_AAA_FAILED:
return "aaa-failed";
case CONNMAN_SERVICE_ERROR_INTERNAL:
return "internal-error";
case CONNMAN_SERVICE_ERROR_DNS_LOOKUP_FAILED:
return "dns-lookup-failed";
case CONNMAN_SERVICE_ERROR_HTTP_GET_FAILED:
return "http-get-failed";
case CONNMAN_SERVICE_ERROR_IPSEC_PSK_AUTH_FAILED:
return "ipsec-psk-auth-failed";
case CONNMAN_SERVICE_ERROR_IPSEC_CERT_AUTH_FAILED:
return "ipsec-cert-auth-failed";
case CONNMAN_SERVICE_ERROR_PPP_AUTH_FAILED:
return "ppp-auth-failed";
default:
break;
}
return NULL;
}
static enum connman_service_error string2error(const char *error)
{
if (g_strcmp0(error, "out-of-range") == 0)
return CONNMAN_SERVICE_ERROR_OUT_OF_RANGE;
else if (g_strcmp0(error, "pin-missing") == 0)
return CONNMAN_SERVICE_ERROR_PIN_MISSING;
else if (g_strcmp0(error, "dhcp-failed") == 0)
return CONNMAN_SERVICE_ERROR_DHCP_FAILED;
else if (g_strcmp0(error, "connect-failed") == 0)
return CONNMAN_SERVICE_ERROR_CONNECT_FAILED;
else if (g_strcmp0(error, "bad-passphrase") == 0)
return CONNMAN_SERVICE_ERROR_BAD_PASSPHRASE;
else if (g_strcmp0(error, "bad-wepkey") == 0)
return CONNMAN_SERVICE_ERROR_BAD_WEPKEY;
else if (g_strcmp0(error, "activation-failed") == 0)
return CONNMAN_SERVICE_ERROR_ACTIVATION_FAILED;
else if (g_strcmp0(error, "need-evdo") == 0)
return CONNMAN_SERVICE_ERROR_NEED_EVDO;
else if (g_strcmp0(error, "need-home-network") == 0)
return CONNMAN_SERVICE_ERROR_NEED_HOME_NETWORK;
else if (g_strcmp0(error, "otasp-failed") == 0)
return CONNMAN_SERVICE_ERROR_OTASP_FAILED;
else if (g_strcmp0(error, "aaa-failed") == 0)
return CONNMAN_SERVICE_ERROR_AAA_FAILED;
else if (g_strcmp0(error, "internal-error") == 0)
return CONNMAN_SERVICE_ERROR_INTERNAL;
return CONNMAN_SERVICE_ERROR_NO_ERROR;
}
static const char *activation_state2string(
enum connman_network_activation_state state)
{
switch (state) {
case CONNMAN_NETWORK_ACTIVATION_STATE_UNKNOWN:
return "unknown";
case CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATED:
return "activated";
case CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATING:
return "activating";
case CONNMAN_NETWORK_ACTIVATION_STATE_NOT_ACTIVATED:
return "not-activated";
case CONNMAN_NETWORK_ACTIVATION_STATE_PARTIALLY_ACTIVATED:
return "partially-activated";
}
return NULL;
}
static const char *network_technology2string(
enum connman_network_cellular_technology technology)
{
switch (technology) {
case CONNMAN_NETWORK_TECHNOLOGY_1XRTT:
return "1xRTT";
case CONNMAN_NETWORK_TECHNOLOGY_EVDO:
return "EVDO";
case CONNMAN_NETWORK_TECHNOLOGY_GSM:
return "GSM";
case CONNMAN_NETWORK_TECHNOLOGY_GPRS:
return "GPRS";
case CONNMAN_NETWORK_TECHNOLOGY_EDGE:
return "EDGE";
case CONNMAN_NETWORK_TECHNOLOGY_UMTS:
return "UMTS";
case CONNMAN_NETWORK_TECHNOLOGY_HSPA:
return "HSPA";
case CONNMAN_NETWORK_TECHNOLOGY_HSPA_PLUS:
return "HSPA+";
case CONNMAN_NETWORK_TECHNOLOGY_LTE:
return "LTE";
case CONNMAN_NETWORK_TECHNOLOGY_LTE_ADVANCED:
return "LTE Advanced";
default:
break;
}
return NULL;
}
static const char *roaming_state2string(
enum connman_network_cellular_roaming_state state)
{
switch (state) {
case CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN:
return "unknown";
case CONNMAN_NETWORK_ROAMING_STATE_HOME:
return "home";
case CONNMAN_NETWORK_ROAMING_STATE_ROAMING:
return "roaming";
}
return NULL;
}
static void property_changed_boolean(struct connman_service *service,
const char *prop_name, connman_bool_t value)
{
if (service->path == NULL)
return;
connman_dbus_send_property_changed_variant(service->path,
CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_BOOLEAN, &value);
}
static void property_changed_string(struct connman_service *service,
const char *prop_name, const char *value)
{
if (service->path == NULL || value == NULL)
return;
connman_dbus_send_property_changed_variant(service->path,
CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_STRING, &value);
}
static void property_changed_int32(struct connman_service *service,
const char *prop_name, int32_t value)
{
if (service->path == NULL)
return;
connman_dbus_send_property_changed_variant(service->path,
CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_INT32, &value);
}
static void property_changed_byte(struct connman_service *service,
const char *prop_name, uint8_t value)
{
if (service->path == NULL)
return;
connman_dbus_send_property_changed_variant(service->path,
CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_BYTE, &value);
}
static struct connman_service *get_active_service(void)
{
struct connman_service *service;
GSequenceIter *iter;
iter = g_sequence_get_begin_iter(service_list);
if (g_sequence_iter_is_end(iter) == TRUE) {
_DBG_SERVICE("no services");
return NULL;
}
service = g_sequence_get(iter);
if (service == NULL) {
_DBG_SERVICE("NULL service");
return NULL;
}
if (!is_connected(service)) {
_DBG_SERVICE("service %p not connected (%s)", service,
connman_service_get_state(service));
return NULL;
}
return service;
}
const char *__connman_service_default(void)
{
return connman_service_get_type(get_active_service());
}
static void notify_state_changed(struct connman_service *service)
{
const char *str;
str = connman_service_get_state(service);
if (str == NULL)
return;
switch (service->state) {
case CONNMAN_SERVICE_STATE_PORTAL:
case CONNMAN_SERVICE_STATE_FAILURE:
case CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE:
property_changed_string(service, "Error",
error2string(service->error));
break;
default:
break;
}
property_changed_string(service, "State", str);
}
static void strength_changed(struct connman_service *service)
{
if (service->strength != 0)
property_changed_byte(service, "Strength", service->strength);
}
/*
* Return TRUE if a passphrase is required to use the service.
*/
static connman_bool_t is_passphrase_required(
const struct connman_service *service)
{
switch (service->security) {
case CONNMAN_SERVICE_SECURITY_WEP:
case CONNMAN_SERVICE_SECURITY_WPA:
case CONNMAN_SERVICE_SECURITY_RSN:
case CONNMAN_SERVICE_SECURITY_PSK:
if (service->passphrase == NULL)
return TRUE; /* must have a passphrase */
if (service->state == CONNMAN_SERVICE_STATE_FAILURE &&
service->error == CONNMAN_SERVICE_ERROR_BAD_PASSPHRASE)
return TRUE; /* bad passphrase */
if (service->favorite == TRUE)
return FALSE; /* successfully used before */
/* if passphrase was never used, assume it is correct */
return is_timeset(&service->last_attempt);
case CONNMAN_SERVICE_SECURITY_802_1X:
/*
* Passphrases are not mandatory for 802.1x - private
* key files may not be encrypted, and authentication
* via PKCS#11 doesn't use passphrases.
*
* TODO(sleffler) true 'cuz we only support EAP-TLS
*/
return FALSE;
default:
break;
}
return FALSE;
}
static connman_bool_t is_key_mgmt_1x(const char *key_mgmt)
{
return (key_mgmt != NULL &&
/* TODO(sleffler) drop case requirement when plugin handles it */
g_strcmp0(key_mgmt, CONNMAN_WIFI_EAP_KEY_MGMT_1X) == 0);
}
static connman_bool_t is_connectable(const struct connman_service *service)
{
if (service->security == CONNMAN_SERVICE_SECURITY_802_1X ||
(service->security == CONNMAN_SERVICE_SECURITY_WEP &&
is_key_mgmt_1x(service->eap.key_mgmt) == TRUE)) {
/* Identity is required for EAP. */
if (service->eap.identity == NULL) {
_DBG_SERVICE("NOT CONNECTABLE: identity is NULL");
return FALSE;
}
/*
* If a client certificate is being used, a private
* key is required.
*/
if ((service->eap.client_cert != NULL ||
service->eap.cert_id != NULL) &&
(service->eap.private_key == NULL &&
service->eap.key_id == NULL)) {
_DBG_SERVICE("NOT CONNECTABLE: client cert but no "
"privkey");
return FALSE;
}
/* If any PKCS#11 data is needed, a PIN is required. */
if ((service->eap.cert_id != NULL ||
service->eap.key_id != NULL ||
service->eap.ca_cert_id != NULL) &&
service->eap.pin == NULL) {
_DBG_SERVICE("NOT CONNECTABLE: pkcs#11 but no PIN");
return FALSE;
}
/*
* There may be more than one EAP type; consider the
* service connectable if at least one combination
* makes sense.
*/
if (service->eap.eap == NULL ||
strstr(service->eap.eap, "TLS") != NULL) {
/* EAP-TLS requires a client certificate. */
if (service->eap.client_cert != NULL ||
service->eap.cert_id != NULL) {
_DBG_SERVICE("CONNECTABLE: EAP-TLS and has a "
"client cert");
return TRUE;
}
}
if (service->eap.eap == NULL ||
strcmp(service->eap.eap, "TLS") != 0) {
/*
* For EAP types other than EAP-TLS,
* identity+password is the minimum
* requirement; the password is used either
* for the outer auth (EAP-MD5) or the inner
* auth (PEAP/MSCHAPv2, EAP-TLS/CHAP,
* etc.... nobody sane tunnels EAP-TLS inside
* of EAP-TTLS, and we don't have all the
* client_cert2, etc. properties needed to
* support that anyway).
*/
if (service->eap.password != NULL) {
_DBG_SERVICE("CONNECTABLE: non-EAP-TLS and "
"has a password");
return TRUE;
}
}
_DBG_SERVICE("NOT CONNECTABLE: no suitable EAP configuration "
"set");
return FALSE;
}
/* TODO(njw): Check WEP keys or WPA passphrases here? */
return TRUE;
}
static void profile_changed(struct connman_service *service)
{
CONNMAN_ASSERT(service->profile != NULL);
property_changed_string(service, "Profile",
__connman_profile_get_path(service->profile));
}
static void get_properties_802_1x(DBusMessageIter *dict,
struct connman_service *service, gboolean has_privs)
{
if (service->eap.identity != NULL)
connman_dbus_dict_append_variant(dict, kEAPIdentity,
DBUS_TYPE_STRING, &service->eap.identity);
if (service->eap.eap != NULL)
connman_dbus_dict_append_variant(dict, kEAPEAP,
DBUS_TYPE_STRING, &service->eap.eap);
if (service->eap.inner_eap != NULL)
connman_dbus_dict_append_variant(dict, kEAPInnerEAP,
DBUS_TYPE_STRING, &service->eap.inner_eap);
if (service->eap.anonymous_identity != NULL)
connman_dbus_dict_append_variant(dict, kEAPAnonymousIdentity,
DBUS_TYPE_STRING, &service->eap.anonymous_identity);
if (service->eap.client_cert != NULL)
connman_dbus_dict_append_variant(dict, kEAPClientCert,
DBUS_TYPE_STRING, &service->eap.client_cert);
if (service->eap.cert_id != NULL)
connman_dbus_dict_append_variant(dict, kEAPCertID,
DBUS_TYPE_STRING, &service->eap.cert_id);
if (service->eap.private_key != NULL)
connman_dbus_dict_append_variant(dict, kEAPPrivateKey,
DBUS_TYPE_STRING, &service->eap.private_key);
if (service->eap.private_key_passwd != NULL && has_privs == TRUE)
connman_dbus_dict_append_variant(dict, kEAPPrivateKeyPassword,
DBUS_TYPE_STRING, &service->eap.private_key_passwd);
if (service->eap.key_id != NULL)
connman_dbus_dict_append_variant(dict, kEAPKeyID,
DBUS_TYPE_STRING, &service->eap.key_id);
if (service->eap.ca_cert != NULL)
connman_dbus_dict_append_variant(dict, kEAPCACert,
DBUS_TYPE_STRING, &service->eap.ca_cert);
if (service->eap.ca_cert_id != NULL)
connman_dbus_dict_append_variant(dict, kEAPCACertID,
DBUS_TYPE_STRING, &service->eap.ca_cert_id);
if (service->eap.ca_cert_nss != NULL)
connman_dbus_dict_append_variant(dict, kEAPCACertNSS,
DBUS_TYPE_STRING, &service->eap.ca_cert_nss);
connman_dbus_dict_append_variant(dict, kEAPUseSystemCAs,
DBUS_TYPE_BOOLEAN, &service->eap.use_system_cas);
if (service->eap.pin != NULL)
connman_dbus_dict_append_variant(dict, kEAPPIN,
DBUS_TYPE_STRING, &service->eap.pin);
if (service->eap.password != NULL && has_privs == TRUE)
connman_dbus_dict_append_variant(dict, kEAPPassword,
DBUS_TYPE_STRING, &service->eap.password);
if (service->eap.key_mgmt != NULL)
connman_dbus_dict_append_variant(dict, kEAPKeyMgmt,
DBUS_TYPE_STRING, &service->eap.key_mgmt);
}
static void append_apn(DBusMessageIter *iter, void *arg)
{
struct connman_network_apn *apn = (struct connman_network_apn *)arg;
if (apn->apn != NULL)
connman_dbus_dict_append_string(iter, "apn", apn->apn);
if (apn->network_id != NULL)
connman_dbus_dict_append_string(iter, "network_id",
apn->network_id);
if (apn->username != NULL)
connman_dbus_dict_append_string(iter, "username",
apn->username);
if (apn->password != NULL)
connman_dbus_dict_append_string(iter, "password",
apn->password);
}
static void get_apn_property(DBusMessageIter *dict,
struct connman_network_apn *apn,
const char *keytag)
{
gchar *apn_key = g_strdup_printf("Cellular.%s", keytag);
connman_dbus_dict_append_string_dict(dict, apn_key, append_apn, apn);
g_free(apn_key);
}
static void append_operator(DBusMessageIter *iter, void *arg)
{
struct connman_network_operator *op =
(struct connman_network_operator *)arg;
if (op->name != NULL)
connman_dbus_dict_append_string(iter, "name", op->name);
if (op->country != NULL)
connman_dbus_dict_append_string(iter, "country", op->country);
if (op->code != NULL)
connman_dbus_dict_append_string(iter, "code", op->code);
}
static void append_online_portal(DBusMessageIter *iter, void *arg)
{
const struct online_portal_data *olp =
(struct online_portal_data *)arg;
if (olp->url != NULL)
connman_dbus_dict_append_string(iter, "url", olp->url);
if (olp->method != NULL)
connman_dbus_dict_append_string(iter, "method", olp->method);
if (olp->postdata != NULL)
connman_dbus_dict_append_string(iter, "postdata", olp->postdata);
}
static const char *check_portal_to_str(enum connman_service_check_portal v)
{
if (v == CONNMAN_SERVICE_CHECK_PORTAL_TRUE)
return "true";
if (v == CONNMAN_SERVICE_CHECK_PORTAL_FALSE)
return "false";
return "auto";
}
static enum connman_service_check_portal check_portal_from_str(const char *str)
{
if (g_ascii_strcasecmp(str, "true") == 0)
return CONNMAN_SERVICE_CHECK_PORTAL_TRUE;
if (g_ascii_strcasecmp(str, "false") == 0)
return CONNMAN_SERVICE_CHECK_PORTAL_FALSE;
return CONNMAN_SERVICE_CHECK_PORTAL_AUTO;
}
static DBusMessage *get_properties(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct connman_service *service = user_data;
DBusMessage *reply;
DBusMessageIter array, dict;
dbus_bool_t required, connectable;
const char *str;
struct connman_device *device;
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);
str = service_type2string(service->type);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "Type",
DBUS_TYPE_STRING, &str);
str = mode2string(service->mode);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "Mode",
DBUS_TYPE_STRING, &str);
str = security2string(service->security);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "Security",
DBUS_TYPE_STRING, &str);
str = connman_service_get_state(service);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "State",
DBUS_TYPE_STRING, &str);
if (service->profile != NULL) {
str = __connman_profile_get_path(service->profile);
connman_dbus_dict_append_variant(&dict, "Profile",
DBUS_TYPE_STRING, &str);
}
str = error2string(service->error);
if (str != NULL)
connman_dbus_dict_append_variant(&dict, "Error",
DBUS_TYPE_STRING, &str);
if (service->pri != CONNMAN_SERVICE_PRI_NONE) {
int pri = service->pri;
connman_dbus_dict_append_variant(&dict, "Priority",
DBUS_TYPE_INT32, &pri);
}
if (service->strength > 0)
connman_dbus_dict_append_variant(&dict, "Strength",
DBUS_TYPE_BYTE, &service->strength);
connman_dbus_dict_append_variant(&dict, "Favorite",
DBUS_TYPE_BOOLEAN, &service->favorite);
connectable = is_connectable(service);
connman_dbus_dict_append_variant(&dict, "Connectable",
DBUS_TYPE_BOOLEAN, &connectable);
if (service->favorite == TRUE)
connman_dbus_dict_append_variant(&dict, "AutoConnect",
DBUS_TYPE_BOOLEAN, &service->autoconnect);
else
connman_dbus_dict_append_variant(&dict, "AutoConnect",
DBUS_TYPE_BOOLEAN, &service->favorite);
connman_dbus_dict_append_variant(&dict, "SaveCredentials",
DBUS_TYPE_BOOLEAN, &service->save_credentials);
str = check_portal_to_str(service->check_portal);
connman_dbus_dict_append_variant(&dict, "CheckPortal",
DBUS_TYPE_STRING, &str);
connman_dbus_dict_append_variant(&dict, "IsActive",
DBUS_TYPE_BOOLEAN, &service->active);
if (service->name != NULL)
connman_dbus_dict_append_variant(&dict, "Name",
DBUS_TYPE_STRING, &service->name);
if (service->guid != NULL)
connman_dbus_dict_append_variant(&dict, "GUID",
DBUS_TYPE_STRING, &service->guid);
if (service->uidata != NULL)
connman_dbus_dict_append_variant(&dict, "UIData",
DBUS_TYPE_STRING, &service->uidata);
device = connman_service_get_device(service);
if (device != NULL) {
const char *path = connman_device_get_path(device);
if (path != NULL)
connman_dbus_dict_append_variant(&dict, "Device",
DBUS_TYPE_OBJECT_PATH, &path);
}
switch (service->type) {
case CONNMAN_SERVICE_TYPE_UNKNOWN:
case CONNMAN_SERVICE_TYPE_ETHERNET:
case CONNMAN_SERVICE_TYPE_WIMAX:
case CONNMAN_SERVICE_TYPE_BLUETOOTH:
break;
case CONNMAN_SERVICE_TYPE_CELLULAR:
if (service->cellular.activation_state !=
CONNMAN_NETWORK_ACTIVATION_STATE_UNKNOWN) {
const char *state_str = activation_state2string(
service->cellular.activation_state);
connman_dbus_dict_append_variant(&dict,
"Cellular.ActivationState", DBUS_TYPE_STRING,
&state_str);
}
if (service->cellular.serving_operator.code != NULL)
connman_dbus_dict_append_string_dict(
&dict,
"Cellular.ServingOperator",
append_operator,
&service->cellular.serving_operator);
if (service->cellular.network_technology !=
CONNMAN_NETWORK_TECHNOLOGY_UNKNOWN) {
const char *tech_str = network_technology2string(
service->cellular.network_technology);
connman_dbus_dict_append_variant(&dict,
"Cellular.NetworkTechnology", DBUS_TYPE_STRING,
&tech_str);
}
if (service->cellular.roaming_state !=
CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN) {
const char *roaming_str = roaming_state2string(
service->cellular.roaming_state);
connman_dbus_dict_append_variant(&dict,
"Cellular.RoamingState", DBUS_TYPE_STRING,
&roaming_str);
}
if (service->cellular.olp.url != NULL) {
connman_dbus_dict_append_string_dict(
&dict,
"Cellular.Olp",
append_online_portal,
&service->cellular.olp);
/* TODO(jglasgow): remove after chrome updated */
connman_dbus_dict_append_variant(&dict,
"Cellular.OlpUrl", DBUS_TYPE_STRING,
&service->cellular.olp.url);
}
if (service->cellular.usage_url != NULL) {
connman_dbus_dict_append_variant(&dict,
"Cellular.UsageUrl", DBUS_TYPE_STRING,
&service->cellular.usage_url);
}
if (service->cellular.apn.apn != NULL)
get_apn_property(&dict, &service->cellular.apn, "APN");
if (service->cellular.last_good_apn.apn != NULL)
get_apn_property(&dict, &service->cellular.last_good_apn,
"LastGoodAPN");
break;
case CONNMAN_SERVICE_TYPE_WIFI:
connman_dbus_dict_append_variant(&dict, "WiFi.HiddenSSID",
DBUS_TYPE_BOOLEAN, &service->wifi.hidden_ssid);
if (service->network != NULL) {
connman_uint16_t frequency;
enum connman_network_phymode phymode;
frequency = connman_network_get_uint16(service->network,
"Frequency");
if (frequency > 0)
connman_dbus_dict_append_variant(&dict,
"WiFi.Frequency", DBUS_TYPE_UINT16,
&frequency);
phymode = connman_network_get_phymode(service->network);
if (phymode != CONNMAN_NETWORK_PHYMODE_UNDEF) {
const char *str =
connman_network_get_phymode_name(phymode);
connman_dbus_dict_append_variant(&dict,
"WiFi.PhyMode", DBUS_TYPE_STRING, &str);
}
str = connman_network_get_string(service->network,
CONNMAN_WIFI_AUTHMODE);
if (str != NULL)
connman_dbus_dict_append_variant(&dict,
CONNMAN_WIFI_AUTHMODE, DBUS_TYPE_STRING,
&str);
str = connman_network_get_string(service->network,
"Address");
if (str != NULL)
connman_dbus_dict_append_variant(&dict,
CONNMAN_WIFI_BSSID, DBUS_TYPE_STRING,
&str);
if (!service->wifi.cached_hex_ssid) {
unsigned int ssid_len;
const void *ssid_blob;
/*
* Read the binary form of the SSID out of
* the network object. If it does not match
* the service name, store a hex-encoded copy
* of the SSID as a property of the service.
*/
ssid_blob = connman_network_get_blob(
service->network, "WiFi.SSID",
&ssid_len);
if (ssid_len != strlen(service->name) ||
memcmp(service->name, ssid_blob,
ssid_len) != 0)
service->wifi.hex_ssid =
connman_network_get_hex_ssid(
service->network);
service->wifi.cached_hex_ssid = TRUE;
}
}
if (service->wifi.hex_ssid != NULL) {
connman_dbus_dict_append_variant(&dict,
"WiFi.HexSSID", DBUS_TYPE_STRING,
&service->wifi.hex_ssid);
}
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_READ_SECRET) == 0) {
if (service->passphrase != NULL)
connman_dbus_dict_append_variant(
&dict, "Passphrase", DBUS_TYPE_STRING,
&service->passphrase);
get_properties_802_1x(&dict, service, TRUE);
} else
get_properties_802_1x(&dict, service, FALSE);
required = is_passphrase_required(service);
connman_dbus_dict_append_variant(&dict, "PassphraseRequired",
DBUS_TYPE_BOOLEAN, &required);
break;
case CONNMAN_SERVICE_TYPE_VPN:
if (service->provider != NULL) {
struct __connman_provider_append_properties_args args;
args.msg = msg;
args.provider = service->provider;
connman_dbus_dict_append_dict(&dict, "Provider",
__connman_provider_append_properties, &args);
}
break;
default:
break;
}
if (service->proxy_config != NULL)
connman_dbus_dict_append_variant(&dict, "ProxyConfig",
DBUS_TYPE_STRING,
&service->proxy_config);
if (service->sticky_route != NULL)
connman_dbus_dict_append_variant(&dict, "StickyHostRoute",
DBUS_TYPE_STRING,
&service->sticky_route->dest);
dbus_message_iter_close_container(&array, &dict);
return reply;
}
static uint8_t hex(const char c)
{
if ('0' <= c && c <= '9')
return c - '0';
else if ('a' <= c && c <= 'f')
return 10 + c - 'a';
else
return 10 + c - 'A';
}
static connman_bool_t cvthexstring(uint8_t data[MAX_WEP_KEYSPACE],
const char *string, int len)
{
int i;
/* NB: check string first before altering data */
for (i = 0; i < len; i++)
if (!isxdigit(string[i])) {
connman_error("invalid char 0x%x in hex key",
string[i]);
return FALSE;
}
for (i = 0; i < len; i += 2)
data[i/2] = (hex(string[i]) << 4) | hex(string[i+1]);
return TRUE;
}
/*
* Validate and convert user-supplied WEP key data.
* Key lengths are 5 or 13 octets of data. When specified as
* ascii hex digits that's 10 or 26 bytes (2 ascii chars = 1 octet).
* An optional wep key index can be specified by including an
* "N:" prefix. An optional C-style "0x" prefix for hex key
* matter is also supported.
*
* When a wep key index is specified we end up with lengths that
* are +2 the 5/13 (or 10/26) values. When an optional 0x
* prefix is given before the key matter that's +2 those values.
*
* TODO(vlaviano): this function could benefit from a unit test; infra?
*/
static connman_bool_t __connman_service_parse_wep_key(const char *data,
connman_uint8_t *wep_key_idx, unsigned int *wep_key_len,
connman_uint8_t wep_key_matter[MAX_WEP_KEYSPACE])
{
int key_idx = 0;
int len = strlen(data);
/* verify string is long enough for parsing checks */
if (len < 5) {
connman_error("wep key too short, len %d", len);
return FALSE;
}
/*
* Handle key index, but only if key length could be valid
* (taking into account a possible 0x prefix for hex keys).
* This allows us to accept ASCII strings that start with
* "0:", "1:", "2:", or "3:".
*/
if (data[1] == ':' &&
(len == 2+5 || len == 2+2*5 || len == 2+2+2*5 ||
len == 2+13 || len == 2+2*13 || len == 2+2+2*13)) {
if (!('0' <= data[0] && data[0] <= '3')) {
connman_error("invalid wep key index \"%c\"", data[0]);
return FALSE;
}
key_idx = data[0] - '0';
data += 2;
len -= 2;
}
/*
* Ignore leading C-style 0x syntax in key material, but only
* if length indicates that this is a hex key rather than an
* ASCII strings. This allows us to handle strings that start
* with 0x or 0X.
*/
if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') &&
(len == 2+2*5 || len == 2+2*13)) {
data += 2;
len -= 2;
}
/*
* Accept 40- and 104-bit keys. Could check for weak keys but not
* worth the effort; this work is to simplify later data handling.
*/
if (len == 2*5 || len == 2*13) {
/* hex string key matter, validate hex digits */
if (!cvthexstring(wep_key_matter, data, len))
return FALSE;
*wep_key_len = len / 2;
*wep_key_idx = key_idx;
return TRUE;
} else if (len == 5 || len == 13) {
/* ascii string, no checking possible, just convert */
memcpy(wep_key_matter, data, len);
*wep_key_len = len;
*wep_key_idx = key_idx;
return TRUE;
} else {
connman_error("invalid wep key length %d", len);
return FALSE;
}
}
/*
* Helper function for validating WPA and RSN passphrases
*/
static connman_bool_t __connman_service_validate_passphrase(const char *data)
{
connman_uint8_t pmk_key_matter[WPA_PMK_KEYLENGTH];
int len = strlen(data);
int i;
/* verify passphrase length is in the range [8-63] */
if (len < 8) {
connman_error("passphrase too short, len %d", len);
return FALSE;
} else if (len == WPA_PMK_KEYLENGTH * 2 &&
cvthexstring(pmk_key_matter, data, len)) {
/* This is a raw PMK string */
return TRUE;
} else if (len > 63) {
connman_error("passphrase too long, len %d", len);
return FALSE;
}
for (i = 0; i < len; i++) {
if (!g_ascii_isprint(data[i])) {
connman_error("passphrase contains non-printable "
"characters");
return FALSE;
}
}
return TRUE;
}
static void service_priority_changed(struct connman_service *service)
{
property_changed_int32(service, "Priority", service->pri);
}
/*
* Helper functions for pushing service state down into the active network.
*/
static void prepare_network_wep(struct connman_service *service)
{
connman_network_set_uint8(service->network, "WiFi.WEPKeyIndex",
service->wifi.wep_key_idx);
connman_network_set_blob(service->network, "WiFi.WEPKey",
service->wifi.wep_key_matter, service->wifi.wep_key_len);
}
static void prepare_network_passphrase(struct connman_service *service)
{
connman_network_set_string(service->network, "WiFi.Passphrase",
service->passphrase);
if (service->security == CONNMAN_SERVICE_SECURITY_WEP)
prepare_network_wep(service);
}
static void prepare_network_string(struct connman_service *service,
const char *name, const char *value)
{
connman_network_set_string(service->network, name, value);
}
static void prepare_network_eap(struct connman_service *service)
{
prepare_network_string(service, CONNMAN_WIFI_EAP_IDENTITY,
service->eap.identity);
prepare_network_string(service, CONNMAN_WIFI_EAP_EAP,
service->eap.eap);
prepare_network_string(service, CONNMAN_WIFI_EAP_INNEREAP,
service->eap.inner_eap);
prepare_network_string(service, CONNMAN_WIFI_EAP_ANONYMOUSIDENTITY,
service->eap.anonymous_identity);
prepare_network_string(service, CONNMAN_WIFI_EAP_CLIENTCERT,
service->eap.client_cert);
prepare_network_string(service, CONNMAN_WIFI_EAP_CERTID,
service->eap.cert_id);
prepare_network_string(service, CONNMAN_WIFI_EAP_PRIVATEKEY,
service->eap.private_key);
prepare_network_string(service, CONNMAN_WIFI_EAP_PRIVATEKEYPASSWORD,
service->eap.private_key_passwd);
prepare_network_string(service, CONNMAN_WIFI_EAP_KEYID,
service->eap.key_id);
prepare_network_string(service, CONNMAN_WIFI_EAP_CACERT,
service->eap.ca_cert);
prepare_network_string(service, CONNMAN_WIFI_EAP_CACERTID,
service->eap.ca_cert_id);
prepare_network_string(service, CONNMAN_WIFI_EAP_CACERTNSS,
service->eap.ca_cert_nss);
connman_network_set_uint8(service->network,
CONNMAN_WIFI_EAP_USESYSTEMCAS,
service->eap.use_system_cas);
prepare_network_string(service, CONNMAN_WIFI_EAP_PIN,
service->eap.pin);
prepare_network_string(service, CONNMAN_WIFI_EAP_PASSWORD,
service->eap.password);
prepare_network_string(service, CONNMAN_WIFI_EAP_KEY_MGMT,
service->eap.key_mgmt);
}
static DBusMessage *set_eap_string(struct connman_service *service,
DBusMessage *msg,
int type,
DBusMessageIter *value,
char **storage,
const char *servicename,
const char *networkname)
{
const char *string;
if (type != DBUS_TYPE_STRING)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(value, &string);
g_free(*storage);
*storage = g_strdup(string);
if (service->network != NULL)
prepare_network_string(service, networkname,
*storage);
property_changed_string(service, servicename, *storage);
service_modified(service);
return NULL;
}
static void clear_str(char **pstr)
{
g_free(*pstr);
*pstr = NULL;
}
static gboolean isvalidstr(const char *str)
{
return (str != NULL && strlen(str) > 0);
}
static void free_apn(struct connman_service *service)
{
struct connman_network_apn *apn = &service->cellular.apn;
clear_str(&apn->apn);
clear_str(&apn->username);
clear_str(&apn->password);
clear_str(&apn->network_id);
}
static DBusMessage *clear_apn(struct connman_service *service, DBusMessage *msg)
{
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
free_apn(service);
connman_dbus_send_property_changed_string_dict(service->path,
CONNMAN_SERVICE_INTERFACE,
"Cellular.APN", append_apn,
&service->cellular.apn);
return NULL;
}
static DBusMessage *set_apn_from_property(struct connman_service *service,
DBusMessage *msg,
gint type,
DBusMessageIter *value)
{
DBusMessageIter array;
struct connman_network_apn *apn = &service->cellular.apn;
const char *apn_value = NULL;
const char *username = NULL;
const char *password = NULL;
const char *network_id = NULL;
_DBG_SERVICE("service %s", service->identifier);
if (type != DBUS_TYPE_ARRAY)
return __connman_error_invalid_arguments(msg);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
free_apn(service);
dbus_message_iter_recurse(value, &array);
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
const char *key;
dbus_message_iter_recurse(&array, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
return __connman_error_invalid_arguments(msg);
if (g_str_equal(key, "apn") == TRUE)
dbus_message_iter_get_basic(&entry, &apn_value);
else if (g_str_equal(key, "network_id"))
dbus_message_iter_get_basic(&entry, &network_id);
else if (g_str_equal(key, "username"))
dbus_message_iter_get_basic(&entry, &username);
else if (g_str_equal(key, "password"))
dbus_message_iter_get_basic(&entry, &password);
else
connman_warn("%s: unknown APN property %s",
__func__, key);
dbus_message_iter_next(&array);
}
if (isvalidstr(apn_value)) {
apn->apn = g_strdup(apn_value);
if (isvalidstr(network_id))
apn->network_id = g_strdup(network_id);
else
apn->network_id =
g_strdup(service->cellular
.serving_operator.code);
if (isvalidstr(username))
apn->username = g_strdup(username);
if (isvalidstr(password))
apn->password = g_strdup(password);
/*
* Clear the last-good-apn, otherwise the one
* the user just set won't be used.
*/
__connman_service_save_last_good_apn(service, NULL);
}
__connman_profile_save_service(service);
connman_dbus_send_property_changed_string_dict(service->path,
CONNMAN_SERVICE_INTERFACE,
"Cellular.APN", append_apn, apn);
return NULL;
}
static void sticky_newroute(void *user_data, int index, int scope,
const char *dst, const char *gateway)
{
struct connman_device *device;
const char *new_gateway;
int err;
struct sticky_route *sr = user_data;
_DBG_SERVICE("");
CONNMAN_ASSERT(sr != NULL);
CONNMAN_ASSERT(sr->service != NULL);
if (sr->index != index)
return;
if (scope != RT_SCOPE_UNIVERSE || g_strcmp0(dst, "0.0.0.0") != 0)
return;
err = connman_inet_del_hostroute(sr->index, sr->ip_addr, sr->gw_addr);
if (err < 0)
connman_warn("%s: del hostroute failed: %d", __func__, err);
device = connman_service_get_device(sr->service);
if (device == NULL) {
connman_error("%s: no device", __func__);
return;
}
sr->index = connman_device_get_index(device);
new_gateway = __connman_ipconfig_get_gateway(sr->index);
if (new_gateway == NULL) {
connman_error("%s: no gateway", __func__);
return;
}
sr->gw_addr = inet_addr(new_gateway);
err = connman_inet_add_hostroute(sr->index, sr->ip_addr, sr->gw_addr);
if (err < 0) {
connman_error("%s: add hostroute failed: %d", __func__, err);
return;
}
}
static struct sticky_route *new_sticky_route(
struct connman_service *service, const char *ip_addr)
{
struct sticky_route *sr;
struct connman_device *device;
const char *gateway;
_DBG_SERVICE("");
CONNMAN_ASSERT(service != NULL);
CONNMAN_ASSERT(ip_addr != NULL);
CONNMAN_ASSERT(service->sticky_route == NULL);
sr = g_try_new0(struct sticky_route, 1);
if (sr == NULL) {
connman_error("%s: no memory", __func__);
return NULL;
}
sr->ip_addr = inet_addr(ip_addr);
device = connman_service_get_device(service);
if (device == NULL) {
connman_error("%s: no device", __func__);
g_free(sr);
return NULL;
}
sr->index = connman_device_get_index(device);
gateway = __connman_ipconfig_get_gateway(sr->index);
if (gateway == NULL) {
connman_error("%s: no gateway", __func__);
g_free(sr);
return NULL;
}
sr->gw_addr = inet_addr(gateway);
sr->dest = g_strdup(ip_addr);
sr->service = service;
sr->rtnl.name = g_strdup(ip_addr);
sr->rtnl.priority = CONNMAN_RTNL_PRIORITY_DEFAULT;
sr->rtnl.private = sr;
sr->rtnl.newroute = sticky_newroute;
sr->rtnl.index = sr->index;
if (connman_rtnl_register(&sr->rtnl) != 0) {
connman_error("%s: rtnl register failed", __func__);
g_free((gpointer)sr->rtnl.name);
g_free(sr->dest);
g_free(sr);
return NULL;
}
return sr;
}
static void del_sticky_route(struct sticky_route *sr)
{
int err;
_DBG_SERVICE("");
CONNMAN_ASSERT(sr != NULL);
connman_rtnl_unregister(&sr->rtnl);
err = connman_inet_del_hostroute(sr->index, sr->ip_addr, sr->gw_addr);
if (err < 0)
connman_warn("%s: del hostroute failed: %d", __func__, err);
g_free((gpointer)sr->rtnl.name);
g_free(sr->dest);
g_free(sr);
}
static DBusMessage *set_property(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct connman_service *service = user_data;
DBusMessageIter iter;
DBusMessage *reply;
const char *name;
if (dbus_message_iter_init(msg, &iter) == FALSE)
return __connman_error_invalid_arguments(msg);
dbus_message_iter_get_basic(&iter, &name);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
_DBG_SERVICE("service %p property %s", service, name);
reply = __service_set_property(service, &iter, msg);
return reply != NULL ?
reply : g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static void state_change(struct connman_service *service,
enum connman_service_state state, int notify)
{
_DBG_SERVICE("service state change (%p) %s -> %s", service,
__statename(service->state), __statename(state));
service->state = state;
g_get_current_time(&service->last_state[state]);
if (notify == TRUE)
__connman_notifier_service_state_changed(service);
}
static void set_idle(struct connman_service *service)
{
service->error = CONNMAN_SERVICE_ERROR_NO_ERROR;
state_change(service, CONNMAN_SERVICE_STATE_IDLE, TRUE);
}
static void service_clear_passphrase(struct connman_service *service)
{
clear_str(&service->passphrase);
service->wifi.wep_key_idx = 0; /* close enough */
service->wifi.wep_key_len = 0;
memset(&service->wifi.wep_key_matter, 0, MAX_WEP_KEYSPACE);
}
static void __connman_service_clear_passphrase(struct connman_service *service)
{
connman_bool_t required;
service_clear_passphrase(service);
required = is_passphrase_required(service);
property_changed_boolean(service, "PassphraseRequired", required);
}
static gboolean service_writeback(gpointer arg)
{
struct connman_service *service = arg;
g_get_current_time(&service->modified);
__connman_profile_save_service(service);
service->writeback_task = 0;
connman_service_unref(service);
return FALSE;
}
static void service_writeback_cancel(struct connman_service *service) {
if (service->writeback_task) {
g_source_remove(service->writeback_task);
service->writeback_task = 0;
connman_service_unref(service);
}
}
static void service_modified(struct connman_service *service)
{
if (!service->writeback_task) {
service->writeback_task =
g_idle_add(service_writeback,
connman_service_ref(service));
}
}
static void service_recheck_portal_state(struct connman_service *service)
{
if (is_connected(service) == FALSE)
return;
if (__connman_service_check_portal(service) == FALSE) {
/*
* If the proxy configuration has changed and the
* service is marked as a portal it is likely because the
* portal check was done without using the http proxy.
* Mark the service as being online.
*/
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_ONLINE);
} else
__connman_portal_service_recheck_state(service);
}
static void service_clear_eap(struct connman_service *service)
{
clear_str(&service->eap.identity);
clear_str(&service->eap.eap);
clear_str(&service->eap.inner_eap);
clear_str(&service->eap.anonymous_identity);
clear_str(&service->eap.client_cert);
clear_str(&service->eap.cert_id);
clear_str(&service->eap.private_key);
clear_str(&service->eap.private_key_passwd);
clear_str(&service->eap.key_id);
clear_str(&service->eap.ca_cert);
clear_str(&service->eap.ca_cert_id);
clear_str(&service->eap.ca_cert_nss);
clear_str(&service->eap.pin);
clear_str(&service->eap.password);
service->eap.use_system_cas = TRUE;
}
void __connman_service_reset_in_memory(struct connman_service *service)
{
connman_service_set_favorite(service, FALSE);
connman_service_set_previously_connected(service, FALSE);
__connman_service_clear_passphrase(service);
service_clear_eap(service);
clear_str(&service->guid);
clear_str(&service->proxy_config);
service->pri = CONNMAN_SERVICE_PRI_NONE;
service->check_portal = CONNMAN_SERVICE_CHECK_PORTAL_AUTO;
service->last_attempt.tv_sec = 0;
service->last_attempt.tv_usec = 0;
/*
* NB: we intentionally do not mark the service modified
* as that will cause it to be written to the profile
* which none of the callers want
*/
}
static DBusMessage *clear_property(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct connman_service *service = user_data;
const char *name;
dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
if (__connman_security_check_privilege(msg,
CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
return __connman_error_permission_denied(msg);
_DBG_SERVICE("service %p property %s", service, name);
if (g_str_equal(name, "Error") == TRUE) {
set_idle(service);
service_modified(service);
} else if (g_str_equal(name, "Passphrase") == TRUE) {
__connman_service_clear_passphrase(service);
service_modified(service);
} else if (g_str_equal(name, kEAPIdentity) == TRUE) {
clear_str(&service->eap.identity);
service_modified(service);
} else if (g_str_equal(name, kEAPEAP) == TRUE) {
clear_str(&service->eap.eap);
service_modified(service);
} else if (g_str_equal(name, kEAPInnerEAP) == TRUE) {
clear_str(&service->eap.inner_eap);
service_modified(service);
} else if (g_str_equal(name, kEAPAnonymousIdentity) == TRUE) {
clear_str(&service->eap.anonymous_identity);
service_modified(service);
} else if (g_str_equal(name, kEAPClientCert) == TRUE) {
clear_str(&service->eap.client_cert);
service_modified(service);
} else if (g_str_equal(name, kEAPCertID) == TRUE) {
clear_str(&service->eap.cert_id);
service_modified(service);
} else if (g_str_equal(name, kEAPPrivateKey) == TRUE) {
clear_str(&service->eap.private_key);
service_modified(service);
} else if (g_str_equal(name, kEAPPrivateKeyPassword) == TRUE) {
clear_str(&service->eap.private_key_passwd);
service_modified(service);
} else if (g_str_equal(name, kEAPKeyID) == TRUE) {
clear_str(&service->eap.key_id);
service_modified(service);
} else if (g_str_equal(name, kEAPCACert) == TRUE) {
clear_str(&service->eap.ca_cert);
service_modified(service);
} else if (g_str_equal(name, kEAPCACertID) == TRUE) {
clear_str(&service->eap.ca_cert_id);
service_modified(service);
} else if (g_str_equal(name, kEAPCACertNSS) == TRUE) {
clear_str(&service->eap.ca_cert_nss);
service_modified(service);
} else if (g_str_equal(name, kEAPPIN) == TRUE) {
clear_str(&service->eap.pin);
service_modified(service);
} else if (g_str_equal(name, kEAPPassword) == TRUE) {
clear_str(&service->eap.password);
service_modified(service);
} else if (g_str_equal(name, kEAPKeyMgmt) == TRUE) {
clear_str(&service->eap.key_mgmt);
service_modified(service);
} else if (g_str_equal(name, "ProxyConfig") == TRUE) {
clear_str(&service->proxy_config);
service_recheck_portal_state(service);
service_modified(service);
} else if (g_str_equal(name, "Priority") == TRUE) {
if (service->pri != CONNMAN_SERVICE_PRI_NONE) {
service->pri = CONNMAN_SERVICE_PRI_NONE;
__service_resort(service);
service_modified(service);
}
} else if (g_str_equal(name, "Cellular.APN") == TRUE) {
DBusMessage *returnmsg = clear_apn(service, msg);
if (returnmsg != NULL)
return returnmsg;
service_modified(service);
} else if (g_str_equal(name, "CheckPortal") == TRUE) {
service->check_portal = CONNMAN_SERVICE_CHECK_PORTAL_AUTO;
service_recheck_portal_state(service);
service_modified(service);
} else if (g_str_equal(name, "UIData") == TRUE) {
clear_str(&service->uidata);
service_modified(service);
} else
return __connman_error_invalid_property(msg);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static connman_bool_t is_connecting(const struct connman_service *service)
{
return (service->state == CONNMAN_SERVICE_STATE_ASSOCIATION ||
service->state == CONNMAN_SERVICE_STATE_CONFIGURATION);
}
static connman_bool_t is_connected(const struct connman_service *service)
{
return (service->state == CONNMAN_SERVICE_STATE_READY ||
service->state == CONNMAN_SERVICE_STATE_PORTAL ||
service->state == CONNMAN_SERVICE_STATE_ONLINE);
}
static connman_bool_t is_portal(const struct connman_service *service)
{
return (service->state == CONNMAN_SERVICE_STATE_PORTAL);
}
static connman_bool_t is_connecting_or_connected(
const struct connman_service *service)
{
return (is_connecting(service) || is_connected(service));
}
connman_bool_t __connman_service_get_connected(
const struct connman_service *service)
{
if (service == NULL)
return FALSE;
return is_connected(service);
}
connman_bool_t __connman_service_get_connecting_or_connected(
const struct connman_service *service)
{
if (service == NULL)
return FALSE;
return is_connecting_or_connected(service);
}
static connman_bool_t is_ignore(const struct connman_service *service)
{
if (service->favorite == FALSE) {
_DBG_SERVICE("SKIP service %s !favorite",
service->identifier);
return TRUE;
}
if (service->autoconnect == FALSE) {
_DBG_SERVICE("SKIP service %s !autoconnect",
service->identifier);
return TRUE;
}
if (service->ignore == TRUE) {
_DBG_SERVICE("SKIP service %s ignore",
service->identifier);
return TRUE;
}
if (__connman_service_check_prepared(service) < 0) {
_DBG_SERVICE("SKIP service %s !prepared",
service->identifier);
return TRUE;
}
return FALSE; /* NB: means good to use */
}
/*
* Search the services list for an autoconnect candidate and, if found,
* kick off a connection request. A device may be specified to restrict
* selection to only services associated with that device. Another
* device can be specified to exclude services associated with a device.
* The latter is typically used together with the NULL device to scan
* for any service not associated with a specific device.
*
* Return TRUE when the request has "completed": either by issuing a
* connection request, queueing the request for later handling, or
* because the request cannot be completed under any circumstances.
*
* Algorithm is:
* 1) if suspending, defer the request
* 2) if a device is specified and it is no longer present
* (has no driver registered) skip the request--this handles
* the case where a device is removed from the system before
* associated services are reclaimed,
* 3a)if a device is specified and another service associated with
* the device is connected or in the process of connecting, skip
* the request
* 3b)if no device is specified and any service is connected or in
* the process of connecting, skip the request
* 4a)if a device is specified, pick the first service associated
* with the device that is enabled for autoconnect and prepared
* 4b)if no device is specified, pick the first service that is
* enabled for autoconnect and prepared
*
* NB: The module-global service_list contains an ordered list of
* services that we can connect to. They are sorted using the
* service_compare function every time a service changes state.
* Therefore we can make some assumptions in the loop below that we
* will encounter items in the connected, is_connecting(), service priority
* and favorite order.
*
* This work is typically done on a device/service state change from
* the idle loop (see below).
*/
static connman_bool_t __auto_connect(struct connman_device *device,
struct connman_device *exclude_device)
{
struct connman_device *service_device;
struct connman_service *service;
GSequenceIter *iter;
_DBG_SERVICE("suspended %d shutdown %d device %s exclude %s",
is_suspended,
is_shutting_down,
device != NULL ?
connman_device_get_interface(device) : NULL,
exclude_device != NULL ?
connman_device_get_interface(exclude_device) : NULL);
/*
* If the system is suspended (suspending/resuming, really, or we
* wouldn't be here) or shutting down, defer the auto-connect until
* later, when we will hopefully be out of the
* is_suspended/is_shutting_down state. (Defer rather than just
* dropping so that the desire-to-autoconnect state is preserved across
* a suspend).
*/
if (is_suspended == TRUE || is_shutting_down == TRUE) {
if (g_queue_find(auto_connect_queue, device) == NULL) {
/* NB: take another ref as the caller drops one */
g_queue_push_tail(auto_connect_queue,
connman_device_ref(device));
_DBG_SERVICE("%s, queue device",
(is_suspended ?
"suspending" : "shutting down"));
}
return TRUE; /* request completed */
}
if (device != NULL) {
/*
* Confirm that the device wasn't invalidated between the
* call to connman_service_auto_connect() and here. If it
* was, then drop the request on the floor.
*/
if (__connman_device_has_driver(device) == FALSE) {
_DBG_SERVICE("device is invalid, ignore request");
return FALSE;
}
}
/*
* Make sure no other service is connecting or connected.
*/
iter = g_sequence_get_begin_iter(service_list);
while (g_sequence_iter_is_end(iter) == FALSE) {
service = g_sequence_get(iter);
service_device = connman_service_get_device(service);
if (device != NULL && service_device != device)
goto next0;
if (is_connecting_or_connected(service) == TRUE) {
_DBG_SERVICE("ABORT service %s state %s",
service->identifier,
connman_service_get_state(service));
return TRUE; /* request completed */
}
next0:
iter = g_sequence_iter_next(iter);
}
/*
* Pick the first service that works and connect.
*/
iter = g_sequence_get_begin_iter(service_list);
while (g_sequence_iter_is_end(iter) == FALSE) {
service = g_sequence_get(iter);
service_device = connman_service_get_device(service);
if (service_device == NULL) {
if (service->provider == NULL) {
/* NB: if provision'd we never have a device */
connman_error("%s: service %s has no device",
__func__, service->identifier);
}
goto next1;
}
if (device != NULL && service_device != device) {
_DBG_SERVICE("SKIP service %s device %s",
service->identifier,
connman_device_get_interface(service_device));
goto next1;
}
if (exclude_device != NULL &&
service_device == exclude_device) {
_DBG_SERVICE("EXCLUDE service %s device %s",
service->identifier,
connman_device_get_interface(service_device));
goto next1;
}
if (service->state != CONNMAN_SERVICE_STATE_IDLE &&
service->state != CONNMAN_SERVICE_STATE_FAILURE) {
_DBG_SERVICE("SKIP service %s state %s",
service->identifier,
connman_service_get_state(service));
goto next1;
}
if (is_ignore(service) == FALSE) {
service->userconnect = FALSE;
_DBG_SERVICE("PICK %s", service->identifier);
__connman_service_connect(service);
return TRUE; /* request completed */
}
next1:
iter = g_sequence_iter_next(iter);
}
return FALSE;
}
static gboolean auto_connect(gpointer data)
{
struct connman_device *device = data;
CONNMAN_ASSERT(device != NULL);
_DBG_SERVICE("device %s", connman_device_get_interface(device));
if (__auto_connect(device, NULL) == FALSE &&
__connman_connection_is_online() == FALSE) {
/*
* No service was found for this device and we are currently
* offline, try again for any other device other than the
* specified device. This handles the case where the highest
* priority device is no longer usable and we want to fallback
* to a service associated with another available device.
*/
__auto_connect(NULL, device);
}
/*
* Drop ref added in __connman_service_device_auto_connect.
*/
connman_device_unref(device);
return FALSE;
}
/**
* Do autoconnect as a deferred operation called from the
* main event loop. Otherwise, we may be performing a
* connect operation from some point that's deeply nested
* in the call stack (e.g., in the middle of handling a
* disconnect), when all the normal preconditions for
* initiating a connection may not yet be established.
*/
void __connman_service_device_auto_connect(struct connman_device *device)
{
CONNMAN_ASSERT(device != NULL);
/*
* Ensure that the device pointer is still valid when
* auto_connect runs.
*/
g_idle_add(auto_connect, connman_device_ref(device));
}
/*
* Attempt autoconnect on the device associated with the specified
* service. Note this is deferred to the main event loop.
*/
void connman_service_auto_connect(struct connman_service *service)
{
struct connman_device *device = connman_service_get_device(service);
/*
* This function is often called from connect_timeout which
* has a reference to the service but not the device. While
* waiting for the connect operation to timeout the device may
* have disappeared.
*/
if (device != NULL)
__connman_service_device_auto_connect(device);
}
static gboolean auto_connect_any(gpointer data)
{
_DBG_SERVICE("");
if (__connman_connection_is_online() == FALSE &&
__auto_connect(NULL, NULL) == FALSE) {
/*
* No service was found to start; kick off scan requests
* on all available devices in case there are networks
* available but without an associated service.
*/
__connman_element_request_scan(CONNMAN_ELEMENT_TYPE_UNKNOWN);
}
return FALSE;
}
/*
* Attempt autoconnect on any device, but only if offline.
*/
void __connman_service_auto_connect_any(void)
{
g_idle_add(auto_connect_any, NULL);
}
/**
* __connman_service_set_activation_state:
* @service: service structure
* @activation state: control interface
* @err: if the activation failed a reason for the failure
*
* Set the activation state of the network
*/
void __connman_service_set_activation_state(struct connman_service *service,
enum connman_network_activation_state activation_state,
enum connman_element_error err)
{
const char *state = activation_state2string(activation_state);
if (err != CONNMAN_ELEMENT_ERROR_NO_ERROR) {
service->error = __connman_element_convert_error(err);
__connman_service_indicate_state(
service,
CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE);
}
service->cellular.activation_state = activation_state;
property_changed_string(service, "Cellular.ActivationState", state);
}
/**
* __connman_service_set_operator:
* @service: service structure
* @op: operator structure
*
* Set operator information for the service
*/
void __connman_service_set_operator(struct connman_service *service,
struct connman_network_operator *op)
{
connman_bool_t changed = FALSE;
if (g_strcmp0(service->cellular.serving_operator.name, op->name) != 0) {
g_free(service->cellular.serving_operator.name);
service->cellular.serving_operator.name = g_strdup(op->name);
changed = TRUE;
}
if (g_strcmp0(service->cellular.serving_operator.code, op->code) != 0) {
g_free(service->cellular.serving_operator.code);
service->cellular.serving_operator.code = g_strdup(op->code);
changed = TRUE;
}
if (g_strcmp0(service->cellular.serving_operator.country,
op->country) != 0) {
g_free(service->cellular.serving_operator.country);
service->cellular.serving_operator.country =
g_strdup(op->country);
changed = TRUE;
}
if (changed) {
(void) connman_dbus_send_property_changed_string_dict(
service->path,
CONNMAN_SERVICE_INTERFACE,
"Cellular.ServingOperator",
append_operator,
&service->cellular.serving_operator);
}
}
/**
* __connman_service_set_olp_url:
* @service: service structure
* @olp_url: online payment url
* @olp_method: online payment method (GET/POST)
* @olp_postdata: online payment data for post
*
* Set online payment url for the service
*/
void __connman_service_set_olp_url(struct connman_service *service,
const char *olp_url,
const char *olp_method,
const char *olp_postdata)
{
connman_bool_t changed = FALSE;
if (g_strcmp0(service->cellular.olp.url, olp_url) != 0) {
g_free(service->cellular.olp.url);
service->cellular.olp.url = g_strdup(olp_url);
changed = TRUE;
/* TODO(jglasgow): remove after chrome updated */
property_changed_string(service, "Cellular.OlpUrl", olp_url);
}
if (g_strcmp0(service->cellular.olp.method, olp_method) != 0) {
g_free(service->cellular.olp.method);
service->cellular.olp.method = g_strdup(olp_method);
changed = TRUE;
}
if (g_strcmp0(service->cellular.olp.postdata, olp_postdata) != 0) {
g_free(service->cellular.olp.postdata);
service->cellular.olp.postdata = g_strdup(olp_postdata);
changed = TRUE;
}
if (changed) {
(void) connman_dbus_send_property_changed_string_dict(
service->path,
CONNMAN_SERVICE_INTERFACE,
"Cellular.Olp",
append_online_portal,
&service->cellular.olp);
}
}
/**
* __connman_service_set_usage_url:
* @service: service structure
* @usage_url: data usage url
*
* Set data usage url for the service
*/
void __connman_service_set_usage_url(struct connman_service *service,
const char *usage_url)
{
if (g_strcmp0(service->cellular.usage_url, usage_url) != 0) {
g_free(service->cellular.usage_url);
service->cellular.usage_url = g_strdup(usage_url);
property_changed_string(service, "Cellular.UsageUrl",
usage_url);
}
}
/**
* connman_service_set_registration_info:
* @network: network structure
* @network_technology: e.g., GPRS, EDGE, HSPA, 1xRTT, EVDO, etc.
* @roaming_state: e.g., Home, Roaming
*
* Set network technology and roaming status information for the service.
*/
void __connman_service_set_registration_info(
struct connman_service *service,
enum connman_network_cellular_technology network_technology,
enum connman_network_cellular_roaming_state roaming_state)
{
if (network_technology != CONNMAN_NETWORK_TECHNOLOGY_UNKNOWN) {
if (service->cellular.network_technology != network_technology) {
property_changed_string(service,
"Cellular.NetworkTechnology",
network_technology2string(network_technology));
}
service->cellular.network_technology = network_technology;
}
if (roaming_state != CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN) {
if (service->cellular.roaming_state != roaming_state) {
property_changed_string(service,
"Cellular.RoamingState",
roaming_state2string(roaming_state));
}
service->cellular.roaming_state = roaming_state;
}
}
static gboolean recheck_portaled_services(gpointer arg);
void connman_service_set_connectivity_state(struct connman_service *service,
enum connman_service_connectivity_state connectivity)
{
if (!is_connected(service))
return;
switch (connectivity) {
case CONNMAN_SERVICE_CONNECTIVITY_STATE_UNRESTRICTED:
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_ONLINE);
break;
case CONNMAN_SERVICE_CONNECTIVITY_STATE_RESTRICTED:
service->error = CONNMAN_SERVICE_ERROR_HTTP_GET_FAILED;
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_PORTAL);
break;
case CONNMAN_SERVICE_CONNECTIVITY_STATE_NONE:
service->error = CONNMAN_SERVICE_ERROR_DNS_LOOKUP_FAILED;
__connman_service_indicate_state(service,
CONNMAN_SERVICE_STATE_PORTAL);
break;
default:
connman_error("invalid connectivity state %d", connectivity);
}
if (is_portal(service) && portal_recheck_timeout == 0)
portal_recheck_timeout =
g_timeout_add_seconds(PORTAL_RECHECK_SECS,
recheck_portaled_services,
NULL);
}
static void remove_timeout(struct connman_service *service)
{
if (service->timeout > 0) {
gboolean ret = g_source_remove(service->timeout);
DBG(DBG_SERVICE | DBG_WIFI, "service %p timeout %d ret %d",
service, service->timeout, ret);
service->timeout = 0;
}
}
static gboolean connect_timeout(gpointer user_data)
{
struct connman_service *service = user_data;
if (service->timeout == 0) {
/*
* This should never happen, but it appears to from time
* to time. This implies that glibc's timer function has
* called us, even though we have already canceled the
* timer!
*/
connman_warn("%s: service timer %p called with a zero timer!",
__func__, service);
return FALSE;
}
DBG(DBG_SERVICE | DBG_WIFI, "service %p timeout %d",
service, service->timeout);
service->timeout = 0;
connman_service_ref(service);
/* NB: do before disconnect to get a valid state change */
__connman_service_indicate_error(service,
CONNMAN_SERVICE_ERROR_CONNECT_FAILED);
if (service->network != NULL)
__connman_network_disconnect(service->network);
else if (service->device != NULL)
__connman_device_disconnect(service->device);
else if (service->provider != NULL)
__connman_provider_disconnect(service->provider);
connman_service_unref(service);
return FALSE;
}
static void set_reconnect_state(struct connman_service *service,
connman_bool_t onoff)
{
struct connman_device *device;
device = connman_service_get_device(service);
if (device != NULL)
__connman_device_set_reconnect(device, onoff);
}
static connman_bool_t get_reconnect_state(struct connman_service *service)
{
struct connman_device *device;
device = connman_service_get_device(service);
return device != NULL ? __connman_device_get_reconnect(device) : FALSE;
}
static struct connman_service *get_connecting_or_connected_service(
enum connman_service_type type)
{
GSequenceIter *iter = g_sequence_get_begin_iter(service_list);</