initial import
[libicd-wpa] / supp.c
diff --git a/supp.c b/supp.c
new file mode 100644 (file)
index 0000000..3877e46
--- /dev/null
+++ b/supp.c
@@ -0,0 +1,598 @@
+/**
+  @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);
+}
+