--- /dev/null
+/**
+ @file supp.c
+
+ Copyright (C) 2009 Javier S. Pedro
+
+ @author Javier S. Pedro <javispedro@javispedro.com>
+
+ This file is part of libicd-network-wpa.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ 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.,
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include <signal.h>
+#include <string.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gconf/gconf-client.h>
+
+#include <osso-ic-dbus.h>
+#include <osso-ic-gconf.h>
+
+#include "log.h"
+#include "common.h"
+#include "icd.h"
+#include "dbus.h"
+#include "dbus-helper.h"
+#include "supp.h"
+
+/* Errors */
+#define WPAS_ERROR_INVALID_NETWORK \
+ WPAS_DBUS_IFACE_INTERFACE ".InvalidNetwork"
+#define WPAS_ERROR_INVALID_BSSID \
+ WPAS_DBUS_IFACE_INTERFACE ".InvalidBSSID"
+
+#define WPAS_ERROR_INVALID_OPTS \
+ WPAS_DBUS_INTERFACE ".InvalidOptions"
+#define WPAS_ERROR_INVALID_IFACE \
+ WPAS_DBUS_INTERFACE ".InvalidInterface"
+
+#define WPAS_ERROR_ADD_ERROR \
+ WPAS_DBUS_INTERFACE ".AddError"
+#define WPAS_ERROR_EXISTS_ERROR \
+ WPAS_DBUS_INTERFACE ".ExistsError"
+#define WPAS_ERROR_REMOVE_ERROR \
+ WPAS_DBUS_INTERFACE ".RemoveError"
+
+#define WPAS_ERROR_SCAN_ERROR \
+ WPAS_DBUS_IFACE_INTERFACE ".ScanError"
+#define WPAS_ERROR_ADD_NETWORK_ERROR \
+ WPAS_DBUS_IFACE_INTERFACE ".AddNetworkError"
+#define WPAS_ERROR_INTERNAL_ERROR \
+ WPAS_DBUS_IFACE_INTERFACE ".InternalError"
+#define WPAS_ERROR_REMOVE_NETWORK_ERROR \
+ WPAS_DBUS_IFACE_INTERFACE ".RemoveNetworkError"
+
+#define WPAS_DBUS_BSSID_FORMAT "%02x%02x%02x%02x%02x%02x"
+
+static pid_t supp_pid = 0;
+static int supp_activation_tries = 0;
+
+static gchar* supp_iface = NULL;
+static gchar* supp_iface_path = NULL;
+
+static gchar* supp_network_id = NULL;
+static gchar* supp_config_path = NULL;
+
+static gboolean supp_configured = FALSE; // Unused right now
+
+/* Callback for supplicant events */
+static supp_cb_fn supp_cb = NULL;
+static gpointer supp_cb_data = NULL;
+
+static void supp_configure();
+
+void supp_set_callback(supp_cb_fn cb, gpointer user_data)
+{
+ supp_cb = cb;
+ supp_cb_data = user_data;
+}
+
+static inline void supp_callback(int result, const char * message)
+{
+ if (supp_cb) supp_cb(result, message, supp_cb_data);
+}
+
+static gboolean supp_set_interface_retry(gpointer data)
+{
+ DLOG_DEBUG(__func__);
+
+ if (supp_pid && supp_iface) supp_set_interface(supp_iface);
+ return FALSE;
+}
+
+static void add_iface_reply_cb(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply;
+ DBusError error;
+
+ DLOG_DEBUG("%s", __func__);
+
+ dbus_error_init(&error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+
+ if (dbus_set_error_from_message(&error, reply)) {
+ DLOG_WARN_L("Error in %s:%s", __func__, error.name);
+
+ if (strcmp(DBUS_ERROR_SERVICE_UNKNOWN, error.name) == 0)
+ {
+ // Supplicant not (yet) active? Try later
+ DLOG_DEBUG("Still waiting for supplicant");
+ supp_activation_tries++;
+ if (supp_activation_tries >= 3) {
+ supp_callback(-1, error.name);
+ } else {
+ g_timeout_add(1000,
+ supp_set_interface_retry,
+ NULL);
+ }
+ } else {
+ supp_callback(-1, error.name);
+ }
+
+ dbus_error_free(&error);
+ } else if (reply) {
+ // Move on to next step
+ gchar* path;
+ if (!dbus_message_get_args(
+ reply, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ {
+ supp_callback(-1, error.name);
+ goto iface_reply_error;
+ }
+
+ supp_iface_path = g_strdup(path);
+ DLOG_DEBUG("Got interface path: %s", supp_iface_path);
+ if (supp_network_id && !supp_config_path) {
+ supp_set_network_id(supp_network_id);
+ } else if (supp_config_path && !supp_configured) {
+ supp_configure();
+ }
+ }
+
+iface_reply_error:
+ if (reply)
+ dbus_message_unref(reply);
+ dbus_pending_call_unref(pending);
+}
+
+void supp_unset_interface()
+{
+ // TODO
+ // mostly uneeded, since we're killing the supplicant instead
+ g_free(supp_iface_path);
+ supp_iface_path = NULL;
+ g_free(supp_iface);
+ supp_iface = NULL;
+}
+
+void supp_set_interface(const char * iface)
+{
+ DBusMessage *msg;
+ DBusPendingCall *pending;
+
+ DLOG_DEBUG("%s: %s", __func__, iface);
+
+ if (supp_iface_path) {
+ supp_unset_interface();
+ }
+
+ if (iface != supp_iface) {
+ if (supp_iface) {
+ g_free(supp_iface);
+ }
+
+ supp_iface = g_strdup(iface);
+ }
+
+ msg = new_dbus_method_call(
+ WPAS_DBUS_SERVICE,
+ WPAS_DBUS_PATH,
+ WPAS_DBUS_INTERFACE,
+ "addInterface");
+
+ append_dbus_args(
+ msg,
+ DBUS_TYPE_STRING, &iface,
+ DBUS_TYPE_INVALID);
+
+ if (!dbus_connection_send_with_reply(get_dbus_connection(),
+ msg, &pending, -1))
+ die("Out of memory");
+
+ if (!dbus_pending_call_set_notify(pending,
+ add_iface_reply_cb, NULL, NULL))
+ die("Out of memory");
+
+ dbus_message_unref(msg);
+}
+
+static void add_network_reply_cb(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply;
+ DBusError error;
+
+ DLOG_DEBUG("%s", __func__);
+
+ dbus_error_init(&error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+
+ if (dbus_set_error_from_message(&error, reply)) {
+ DLOG_WARN_L("Error in %s:%s", __func__, error.name);
+
+ supp_callback(-1, error.name);
+ dbus_error_free(&error);
+ } else if (reply) {
+ // Move on to next step
+ gchar* path;
+ if (!dbus_message_get_args(
+ reply, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ {
+ supp_callback(-1, error.name);
+ goto net_reply_error;
+ }
+
+ supp_config_path = g_strdup(path);
+ DLOG_DEBUG("Got network path: %s", supp_iface_path);
+ if (supp_iface_path && !supp_configured) {
+ supp_configure();
+ }
+ }
+
+net_reply_error:
+ if (reply)
+ dbus_message_unref(reply);
+ dbus_pending_call_unref(pending);
+}
+
+void supp_unset_network_id()
+{
+ g_free(supp_config_path);
+ supp_config_path = NULL;
+ g_free(supp_network_id);
+ supp_network_id = NULL;
+ // TODO
+}
+
+void supp_set_network_id(const char * network_id)
+{
+ DBusMessage *msg;
+ DBusPendingCall *pending;
+
+ DLOG_DEBUG("%s: %s", __func__, network_id);
+
+ if (supp_config_path) {
+ supp_unset_network_id();
+ }
+
+ if (network_id != supp_network_id) {
+ if (supp_network_id) {
+ g_free(supp_network_id);
+ }
+
+ supp_network_id = g_strdup(network_id);
+ }
+
+ if (!supp_iface_path) {
+ DLOG_DEBUG("Deferring network creation");
+ return;
+ }
+
+ msg = new_dbus_method_call(
+ WPAS_DBUS_SERVICE,
+ supp_iface_path,
+ WPAS_DBUS_IFACE_INTERFACE,
+ "addNetwork");
+
+ if (!dbus_connection_send_with_reply(get_dbus_connection(),
+ msg, &pending, -1))
+ die("Out of memory");
+
+ if (!dbus_pending_call_set_notify(pending,
+ add_network_reply_cb, NULL, NULL))
+ die("Out of memory");
+
+ dbus_message_unref(msg);
+}
+
+static gchar * wpas_params[3] = { "/sbin/wpa_supplicant", "-u", NULL };
+
+int supp_enable()
+{
+ DLOG_DEBUG("%s", __func__);
+
+ supp_activation_tries = 0;
+
+ GError* error;
+ GPid pid;
+ if (!g_spawn_async(NULL, wpas_params, NULL,
+ G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid, &error)) {
+ DLOG_ERR("Couldn't spawn supplicant: %s", error->message);
+ return -1;
+ }
+
+ supp_pid = pid;
+
+ DLOG_INFO("Spawned %s , pid %d", wpas_params[0], pid);
+
+ icd_watch_pid(supp_pid);
+
+ return 0;
+}
+
+void supp_disable(void)
+{
+ if (supp_pid) {
+ DLOG_INFO("Killing supplicant (pid %d)", supp_pid);
+ kill(supp_pid, SIGTERM);
+ }
+
+ // Consider everything as deconfigured
+ g_free(supp_iface);
+ supp_iface = NULL;
+ g_free(supp_iface_path);
+ supp_iface_path = NULL;
+ g_free(supp_network_id);
+ supp_network_id = NULL;
+ g_free(supp_config_path);
+ supp_config_path = NULL;
+
+ supp_configured = FALSE;
+}
+
+int supp_is_active(void)
+{
+ return supp_pid ? TRUE : FALSE;
+}
+
+void supp_handle_signal(gchar* old_state, gchar* new_state)
+{
+ DLOG_DEBUG("Supplicant StateChange %s -> %s", old_state, new_state);
+
+ if (strcmp(new_state, "COMPLETED") == 0)
+ {
+ supp_callback(SUPP_STATUS_CONNECTED, new_state);
+ } else if (strcmp(new_state, "DISCONNECTED") == 0) {
+ supp_callback(SUPP_STATUS_DISCONNECTED, new_state);
+ }
+}
+
+void supp_handle_killed(void)
+{
+ DLOG_DEBUG("%s", __func__);
+
+ g_spawn_close_pid(supp_pid);
+ supp_pid = 0;
+
+ supp_disable();
+
+ supp_callback(SUPP_STATUS_KILLED, NULL);
+}
+
+static void free_settings_item(gpointer data, gpointer user_data)
+{
+ gconf_entry_free(data);
+}
+
+static void enable_reply_cb(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply;
+ DBusError error;
+
+ DLOG_DEBUG("%s", __func__);
+
+ dbus_error_init(&error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+
+ if (dbus_set_error_from_message(&error, reply)) {
+ DLOG_WARN_L("Error in %s:%s", __func__, error.name);
+
+ supp_callback(SUPP_STATUS_ERROR, error.name);
+ dbus_error_free(&error);
+ }
+
+ if (reply)
+ dbus_message_unref(reply);
+ dbus_pending_call_unref(pending);
+}
+
+static void supp_enable_network()
+{
+ DBusMessage* message; //The full mesage we are going to send.
+ DBusPendingCall *pending;
+
+ message = dbus_message_new_method_call(
+ WPAS_DBUS_SERVICE,
+ supp_config_path,
+ WPAS_DBUS_IFACE_NETWORK,
+ WPAS_ENABLE_NETWORK_METHOD
+ );
+ if (!message) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ return;
+ }
+
+ // Send message
+ if (!dbus_connection_send_with_reply(get_dbus_connection(),
+ message, &pending, -1)) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ goto send_error;
+ }
+
+ if (!dbus_pending_call_set_notify(pending,
+ enable_reply_cb, NULL, NULL)) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ }
+
+ // Fall through
+send_error:
+ dbus_message_unref(message);
+}
+
+static void configure_reply_cb(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply;
+ DBusError error;
+
+ DLOG_DEBUG("%s", __func__);
+
+ dbus_error_init(&error);
+
+ reply = dbus_pending_call_steal_reply(pending);
+
+ if (dbus_set_error_from_message(&error, reply)) {
+ DLOG_WARN_L("Error in %s:%s", __func__, error.name);
+
+ supp_callback(-1, error.name);
+ dbus_error_free(&error);
+ } else if (reply) {
+ supp_enable_network();
+ }
+
+ if (reply)
+ dbus_message_unref(reply);
+ dbus_pending_call_unref(pending);
+}
+
+static void supp_configure()
+{
+ // This is going to be long
+ DLOG_DEBUG("%s: %s", __func__, supp_network_id);
+ GConfClient *client = gconf_client_get_default();
+ GError *error = NULL;
+
+ DBusMessage* message; //The full mesage we are going to send.
+ DBusMessageIter iter, iter_dict;
+ DBusPendingCall *pending;
+
+ if (!client) {
+ DLOG_ERR("Cannot get gconf client");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ return;
+ }
+
+ gchar * settings_path = g_strconcat(ICD_GCONF_PATH,
+ "/", supp_network_id, NULL);
+
+ GSList * settings = gconf_client_all_entries(client,
+ settings_path, &error);
+ if (error) {
+ DLOG_ERR("Could not get setting:%s, error:%s", settings_path,
+ error->message);
+ g_free(settings_path);
+ g_clear_error(&error);
+ g_object_unref(client);
+ supp_callback(-1, error->message);
+ return;
+ }
+
+ message = dbus_message_new_method_call(
+ WPAS_DBUS_SERVICE,
+ supp_config_path,
+ WPAS_DBUS_IFACE_NETWORK,
+ WPAS_SET_NETWORK_METHOD
+ );
+ if (!message) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ goto msg_init_error;
+ }
+
+ dbus_message_iter_init_append(message, &iter);
+ if (dbus_dict_open_write(&iter, &iter_dict) != 0) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ goto dict_init_error;
+ }
+
+ DLOG_DEBUG("Preparing to send %d settings", g_slist_length(settings));
+
+ GSList* i;
+ for (i = settings; i; i = g_slist_next(i)) {
+ GConfEntry* entry = i->data;
+ gchar * key = g_path_get_basename(gconf_entry_get_key(entry));
+ GConfValue* value = gconf_entry_get_value(entry);
+
+ if (g_ascii_strncasecmp(key,
+ WPA_GCONF_SETTING_PREFIX,
+ WPA_GCONF_SETTING_PREFIX_LEN)) {
+ g_free(key);
+ continue;
+ }
+
+ // Skip prefix
+ key += WPA_GCONF_SETTING_PREFIX_LEN;
+
+ switch (value->type) {
+ case GCONF_VALUE_STRING:
+ DLOG_DEBUG("Setting string %s = %s",
+ key, gconf_value_get_string(value));
+ dbus_dict_append_string(&iter_dict,
+ key, gconf_value_get_string(value));
+ break;
+
+ case GCONF_VALUE_INT:
+ DLOG_DEBUG("Setting int32 %s = %d",
+ key, gconf_value_get_int(value));
+ dbus_dict_append_int32(&iter_dict,
+ key, gconf_value_get_int(value));
+ break;
+ default:
+ DLOG_DEBUG("Unknown setting type for %s",
+ key);
+ break;
+ }
+
+ key -= WPA_GCONF_SETTING_PREFIX_LEN;
+ g_free(key);
+ }
+
+ if (dbus_dict_close_write(&iter, &iter_dict) != 0) {
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ goto dict_close_error;
+ }
+
+ // Send message
+ if (!dbus_connection_send_with_reply(get_dbus_connection(),
+ message, &pending, -1)) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ goto send_error;
+ }
+
+ if (!dbus_pending_call_set_notify(pending,
+ configure_reply_cb, NULL, NULL)) {
+ DLOG_CRIT_L("Out of memory");
+ supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
+ goto send_error;
+ }
+
+ // Fall through
+send_error:
+dict_close_error:
+dict_init_error:
+ dbus_message_unref(message);
+msg_init_error:
+ g_free(settings_path);
+
+ g_slist_foreach(settings, free_settings_item, NULL);
+ g_slist_free(settings);
+
+ g_object_unref(client);
+}
+