new fremantle branch
[libicd-wpa] / supp.c
1 /**
2   @file supp.c
3
4   Copyright (C) 2009 Javier S. Pedro
5
6   @author Javier S. Pedro <javispedro@javispedro.com>
7
8   This file is part of libicd-network-wpa.
9
10   This program is free software; you can redistribute it and/or modify it
11   under the terms of the GNU General Public License as published by the
12   Free Software Foundation; either version 2 of the License, or (at your
13   option) any later version.
14
15   This program is distributed in the hope that it will be useful, but
16   WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18   General Public License for more details.
19
20   You should have received a copy of the GNU General Public License along
21   with this program; if not, write to the Free Software Foundation, Inc.,
22   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
24 */
25
26 #include <signal.h>
27 #include <string.h>
28
29 #include <glib.h>
30 #include <dbus/dbus.h>
31 #include <gconf/gconf-client.h>
32
33 #include <osso-ic-dbus.h>
34 #include <osso-ic-gconf.h>
35
36 #include "log.h"
37 #include "common.h"
38 #include "icd.h"
39 #include "dbus.h"
40 #include "dbus-helper.h"
41 #include "supp.h"
42
43 /* Errors */
44 #define WPAS_ERROR_INVALID_NETWORK \
45         WPAS_DBUS_IFACE_INTERFACE ".InvalidNetwork"
46 #define WPAS_ERROR_INVALID_BSSID \
47         WPAS_DBUS_IFACE_INTERFACE ".InvalidBSSID"
48
49 #define WPAS_ERROR_INVALID_OPTS \
50         WPAS_DBUS_INTERFACE ".InvalidOptions"
51 #define WPAS_ERROR_INVALID_IFACE \
52         WPAS_DBUS_INTERFACE ".InvalidInterface"
53
54 #define WPAS_ERROR_ADD_ERROR \
55         WPAS_DBUS_INTERFACE ".AddError"
56 #define WPAS_ERROR_EXISTS_ERROR \
57         WPAS_DBUS_INTERFACE ".ExistsError"
58 #define WPAS_ERROR_REMOVE_ERROR \
59         WPAS_DBUS_INTERFACE ".RemoveError"
60
61 #define WPAS_ERROR_SCAN_ERROR \
62         WPAS_DBUS_IFACE_INTERFACE ".ScanError"
63 #define WPAS_ERROR_ADD_NETWORK_ERROR \
64         WPAS_DBUS_IFACE_INTERFACE ".AddNetworkError"
65 #define WPAS_ERROR_INTERNAL_ERROR \
66         WPAS_DBUS_IFACE_INTERFACE ".InternalError"
67 #define WPAS_ERROR_REMOVE_NETWORK_ERROR \
68         WPAS_DBUS_IFACE_INTERFACE ".RemoveNetworkError"
69
70 #define WPAS_DBUS_BSSID_FORMAT "%02x%02x%02x%02x%02x%02x"
71
72 static pid_t supp_pid = 0;
73 static int supp_activation_tries = 0;
74
75 static gchar* supp_iface = NULL;
76 static gchar* supp_iface_path = NULL;
77
78 static gchar* supp_network_id = NULL;
79 static gchar* supp_config_path = NULL;
80
81 static gboolean supp_configured = FALSE; // Unused right now
82
83 /* Callback for supplicant events */
84 static supp_cb_fn supp_cb = NULL;
85 static gpointer supp_cb_data = NULL;
86
87 static void supp_configure();
88
89 void supp_set_callback(supp_cb_fn cb, gpointer user_data)
90 {
91         supp_cb = cb;
92         supp_cb_data = user_data;
93 }
94
95 static inline void supp_callback(int result, const char * message)
96 {
97         if (supp_cb) supp_cb(result, message, supp_cb_data);
98 }
99
100 static gboolean supp_set_interface_retry(gpointer data)
101 {
102         DLOG_DEBUG(__func__);
103         
104         if (supp_pid && supp_iface) supp_set_interface(supp_iface);
105         return FALSE;
106 }
107
108 static void add_iface_reply_cb(DBusPendingCall *pending, void *user_data)
109 {
110         DBusMessage *reply;
111         DBusError error;
112
113         DLOG_DEBUG("%s", __func__);
114         
115         dbus_error_init(&error);
116         
117         reply = dbus_pending_call_steal_reply(pending);
118         
119         if (dbus_set_error_from_message(&error, reply)) {
120                 DLOG_WARN_L("Error in %s:%s", __func__, error.name);
121                 
122                 if (strcmp(DBUS_ERROR_SERVICE_UNKNOWN, error.name) == 0)
123                 {
124                         // Supplicant not (yet) active? Try later
125                         DLOG_DEBUG("Still waiting for supplicant");
126                         supp_activation_tries++;
127                         if (supp_activation_tries >= 3) {
128                                  supp_callback(-1, error.name);
129                         } else {
130                                  g_timeout_add(1000,
131                                                 supp_set_interface_retry,
132                                                 NULL);
133                         }
134                 } else {              
135                         supp_callback(-1, error.name);
136                 }
137                 
138                 dbus_error_free(&error);
139         } else if (reply) {
140                 // Move on to next step
141                 gchar* path;
142                 if (!dbus_message_get_args(
143                         reply, NULL,
144                         DBUS_TYPE_OBJECT_PATH, &path,
145                         DBUS_TYPE_INVALID))
146                 {
147                         supp_callback(-1, error.name);
148                         goto iface_reply_error;
149                 }
150                 
151                 supp_iface_path = g_strdup(path);
152                 DLOG_DEBUG("Got interface path: %s", supp_iface_path);
153                 if (supp_network_id && !supp_config_path) {
154                         supp_set_network_id(supp_network_id);
155                 } else if (supp_config_path && !supp_configured) {
156                         supp_configure();
157                 }
158         }
159     
160 iface_reply_error:    
161         if (reply) 
162                 dbus_message_unref(reply);
163         dbus_pending_call_unref(pending);
164 }
165
166 void supp_unset_interface()
167 {
168         // TODO
169         // mostly uneeded, since we're killing the supplicant instead
170         g_free(supp_iface_path);
171         supp_iface_path = NULL;
172         g_free(supp_iface);
173         supp_iface = NULL;
174 }
175
176 void supp_set_interface(const char * iface)
177 {
178         DBusMessage *msg;
179         DBusPendingCall *pending;
180         
181         DLOG_DEBUG("%s: %s", __func__, iface);
182   
183         if (supp_iface_path) {
184                 supp_unset_interface();
185         }
186         
187         if (iface != supp_iface) {
188                 if (supp_iface) {
189                         g_free(supp_iface);
190                 }
191
192                 supp_iface = g_strdup(iface);
193         }
194         
195         msg = new_dbus_method_call(
196                 WPAS_DBUS_SERVICE,
197                 WPAS_DBUS_PATH,
198                 WPAS_DBUS_INTERFACE,
199                 "addInterface");
200         
201         append_dbus_args(
202                 msg,
203                 DBUS_TYPE_STRING, &iface,               
204                 DBUS_TYPE_INVALID);
205
206         if (!dbus_connection_send_with_reply(get_dbus_connection(), 
207                                              msg, &pending, -1))
208                 die("Out of memory");
209         
210         if (!dbus_pending_call_set_notify(pending,
211                 add_iface_reply_cb, NULL, NULL))
212                 die("Out of memory");
213         
214         dbus_message_unref(msg);        
215 }
216
217 static void add_network_reply_cb(DBusPendingCall *pending, void *user_data)
218 {
219         DBusMessage *reply;
220         DBusError error;
221
222         DLOG_DEBUG("%s", __func__);
223         
224         dbus_error_init(&error);
225         
226         reply = dbus_pending_call_steal_reply(pending);
227         
228         if (dbus_set_error_from_message(&error, reply)) {
229                 DLOG_WARN_L("Error in %s:%s", __func__, error.name);
230                 
231                 supp_callback(-1, error.name);
232                 dbus_error_free(&error);
233         } else if (reply) {
234                 // Move on to next step
235                 gchar* path;
236                 if (!dbus_message_get_args(
237                         reply, NULL,
238                         DBUS_TYPE_OBJECT_PATH, &path,
239                         DBUS_TYPE_INVALID))
240                 {
241                         supp_callback(-1, error.name);
242                         goto net_reply_error;
243                 }
244                 
245                 supp_config_path = g_strdup(path);
246                 DLOG_DEBUG("Got network path: %s", supp_iface_path);
247                 if (supp_iface_path && !supp_configured) {
248                         supp_configure();
249                 }
250         }
251     
252 net_reply_error:    
253         if (reply) 
254                 dbus_message_unref(reply);
255         dbus_pending_call_unref(pending);
256 }
257
258 void supp_unset_network_id()
259 {
260         g_free(supp_config_path);
261         supp_config_path = NULL;
262         g_free(supp_network_id);
263         supp_network_id = NULL;
264         // TODO
265 }
266
267 void supp_set_network_id(const char * network_id)
268 {
269         DBusMessage *msg;
270         DBusPendingCall *pending;
271         
272         DLOG_DEBUG("%s: %s", __func__, network_id);
273         
274         if (supp_config_path) {
275                 supp_unset_network_id();
276         }
277         
278         if (network_id != supp_network_id) {
279                 if (supp_network_id) {
280                         g_free(supp_network_id);
281                 }
282
283                 supp_network_id = g_strdup(network_id);
284         }
285         
286         if (!supp_iface_path) {
287                 DLOG_DEBUG("Deferring network creation");
288                 return;
289         }
290         
291         msg = new_dbus_method_call(
292                 WPAS_DBUS_SERVICE,
293                 supp_iface_path,
294                 WPAS_DBUS_IFACE_INTERFACE,
295                 "addNetwork");
296  
297         if (!dbus_connection_send_with_reply(get_dbus_connection(), 
298                                              msg, &pending, -1))
299                 die("Out of memory");
300         
301         if (!dbus_pending_call_set_notify(pending,
302                 add_network_reply_cb, NULL, NULL))
303                 die("Out of memory");
304         
305         dbus_message_unref(msg);
306 }
307
308 static gchar * wpas_params[3] = { "/sbin/wpa_supplicant", "-u", NULL };
309
310 int supp_enable()
311 {
312         DLOG_DEBUG("%s", __func__);
313         
314         supp_activation_tries = 0;
315         
316         GError* error;
317         GPid pid;
318         if (!g_spawn_async(NULL, wpas_params, NULL, 
319                 G_SPAWN_DO_NOT_REAP_CHILD,
320                 NULL, NULL, &pid, &error)) {
321                 DLOG_ERR("Couldn't spawn supplicant: %s", error->message);
322                 return -1;
323         }
324
325         supp_pid = pid;
326         
327         DLOG_INFO("Spawned %s , pid %d", wpas_params[0], pid);
328         
329         icd_watch_pid(supp_pid);
330         
331         return 0;
332 }
333
334 void supp_disable(void)
335 {
336         if (supp_pid) {
337                 DLOG_INFO("Killing supplicant (pid %d)", supp_pid);
338                 kill(supp_pid, SIGTERM);
339         }
340         
341         // Consider everything as deconfigured
342         g_free(supp_iface);
343         supp_iface = NULL;
344         g_free(supp_iface_path);
345         supp_iface_path = NULL;
346         g_free(supp_network_id);
347         supp_network_id = NULL;
348         g_free(supp_config_path);
349         supp_config_path = NULL;
350         
351         supp_configured = FALSE;
352 }
353
354 int supp_is_active(void)
355 {
356         return supp_pid ? TRUE : FALSE;
357 }
358
359 void supp_handle_signal(gchar* old_state, gchar* new_state)
360 {
361         DLOG_DEBUG("Supplicant StateChange %s -> %s", old_state, new_state);
362         
363         if (strcmp(new_state, "COMPLETED") == 0)
364         {
365                 supp_callback(SUPP_STATUS_CONNECTED, new_state);
366         } else if (strcmp(new_state, "DISCONNECTED") == 0) {
367                 supp_callback(SUPP_STATUS_DISCONNECTED, new_state);
368         }
369 }
370
371 void supp_handle_killed(void)
372 {
373         DLOG_DEBUG("%s", __func__);
374
375         g_spawn_close_pid(supp_pid);
376         supp_pid = 0;
377         
378         supp_disable();
379
380         supp_callback(SUPP_STATUS_KILLED, NULL);
381 }
382
383 static void free_settings_item(gpointer data, gpointer user_data)
384 {
385         gconf_entry_free(data);
386 }
387
388 static void enable_reply_cb(DBusPendingCall *pending, void *user_data)
389 {
390         DBusMessage *reply;
391         DBusError error;
392
393         DLOG_DEBUG("%s", __func__);
394         
395         dbus_error_init(&error);
396         
397         reply = dbus_pending_call_steal_reply(pending);
398         
399         if (dbus_set_error_from_message(&error, reply)) {
400                 DLOG_WARN_L("Error in %s:%s", __func__, error.name);
401                 
402                 supp_callback(SUPP_STATUS_ERROR, error.name);
403                 dbus_error_free(&error);
404         }
405        
406         if (reply)
407                 dbus_message_unref(reply);
408         dbus_pending_call_unref(pending);
409 }
410
411 static void supp_enable_network()
412 {
413         DBusMessage* message; //The full mesage we are going to send.
414         DBusPendingCall *pending;
415         
416         message = dbus_message_new_method_call(
417                 WPAS_DBUS_SERVICE,
418                 supp_config_path,
419                 WPAS_DBUS_IFACE_NETWORK,
420                 WPAS_ENABLE_NETWORK_METHOD
421         );
422         if (!message) {
423                 DLOG_CRIT_L("Out of memory");
424                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
425                 return;
426         }
427         
428         // Send message
429         if (!dbus_connection_send_with_reply(get_dbus_connection(), 
430                                              message, &pending, -1)) {
431                 DLOG_CRIT_L("Out of memory");
432                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
433                 goto send_error;
434         }
435         
436         if (!dbus_pending_call_set_notify(pending, 
437                 enable_reply_cb, NULL, NULL)) {
438                 DLOG_CRIT_L("Out of memory");
439                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
440         }
441         
442         // Fall through
443 send_error:
444         dbus_message_unref(message);
445 }
446
447 static void configure_reply_cb(DBusPendingCall *pending, void *user_data)
448 {
449         DBusMessage *reply;
450         DBusError error;
451
452         DLOG_DEBUG("%s", __func__);
453         
454         dbus_error_init(&error);
455         
456         reply = dbus_pending_call_steal_reply(pending);
457         
458         if (dbus_set_error_from_message(&error, reply)) {
459                 DLOG_WARN_L("Error in %s:%s", __func__, error.name);
460                 
461                 supp_callback(-1, error.name);
462                 dbus_error_free(&error);
463         } else if (reply) {
464                 supp_enable_network();
465         }
466        
467         if (reply)
468                 dbus_message_unref(reply);
469         dbus_pending_call_unref(pending);
470 }
471
472 static void supp_configure()
473 {
474         // This is going to be long
475         DLOG_DEBUG("%s: %s", __func__, supp_network_id);
476         GConfClient *client = gconf_client_get_default();
477         GError *error = NULL;
478         
479         DBusMessage* message; //The full mesage we are going to send.
480         DBusMessageIter iter, iter_dict;
481         DBusPendingCall *pending;
482         
483         if (!client) {
484                 DLOG_ERR("Cannot get gconf client");
485                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
486                 return;
487         }
488         
489         gchar * settings_path = g_strconcat(ICD_GCONF_PATH,
490                 "/", supp_network_id, NULL);
491         
492         GSList * settings = gconf_client_all_entries(client,
493                 settings_path, &error);
494         if (error) {
495                 DLOG_ERR("Could not get setting:%s, error:%s", settings_path, 
496                          error->message);
497                 g_free(settings_path);
498                 g_clear_error(&error);
499                 g_object_unref(client);
500                 supp_callback(-1, error->message);
501                 return;
502         }
503         
504         message = dbus_message_new_method_call(
505                 WPAS_DBUS_SERVICE,
506                 supp_config_path,
507                 WPAS_DBUS_IFACE_NETWORK,
508                 WPAS_SET_NETWORK_METHOD
509         );
510         if (!message) {
511                 DLOG_CRIT_L("Out of memory");
512                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
513                 goto msg_init_error;
514         }
515         
516         dbus_message_iter_init_append(message, &iter);
517         if (dbus_dict_open_write(&iter, &iter_dict) != 0) {
518                 DLOG_CRIT_L("Out of memory");
519                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
520                 goto dict_init_error;
521         }
522         
523         DLOG_DEBUG("Preparing to send %d settings", g_slist_length(settings));
524         
525         GSList* i;
526         for (i = settings; i; i = g_slist_next(i)) {
527                 GConfEntry* entry = i->data;
528                 gchar * key = g_path_get_basename(gconf_entry_get_key(entry));
529                 GConfValue* value = gconf_entry_get_value(entry);
530                 
531                 if (g_ascii_strncasecmp(key,
532                         WPA_GCONF_SETTING_PREFIX, 
533                         WPA_GCONF_SETTING_PREFIX_LEN)) {
534                         g_free(key);
535                         continue;
536                 }
537                 
538                 // Skip prefix
539                 key += WPA_GCONF_SETTING_PREFIX_LEN;
540                 
541                 switch (value->type) {
542                 case GCONF_VALUE_STRING:
543                         DLOG_DEBUG("Setting string %s = %s",
544                                 key, gconf_value_get_string(value));
545                         dbus_dict_append_string(&iter_dict, 
546                                 key, gconf_value_get_string(value));
547                         break;
548                         
549                 case GCONF_VALUE_INT:
550                         DLOG_DEBUG("Setting int32 %s = %d",
551                                 key, gconf_value_get_int(value));
552                         dbus_dict_append_int32(&iter_dict,
553                                 key, gconf_value_get_int(value));
554                         break;
555                 default:
556                         DLOG_DEBUG("Unknown setting type for %s",
557                                 key);
558                         break;
559                 }
560                 
561                 key -= WPA_GCONF_SETTING_PREFIX_LEN;
562                 g_free(key);
563         }
564         
565         if (dbus_dict_close_write(&iter, &iter_dict) != 0) {
566                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
567                 goto dict_close_error;
568         }
569                 
570         // Send message
571         if (!dbus_connection_send_with_reply(get_dbus_connection(), 
572                                              message, &pending, -1)) {
573                 DLOG_CRIT_L("Out of memory");
574                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
575                 goto send_error;
576         }
577         
578         if (!dbus_pending_call_set_notify(pending, 
579                 configure_reply_cb, NULL, NULL)) {
580                 DLOG_CRIT_L("Out of memory");
581                 supp_callback(-1, ICD_DBUS_ERROR_SYSTEM_ERROR);
582                 goto send_error;
583         }
584         
585         // Fall through
586 send_error:
587 dict_close_error:
588 dict_init_error:
589         dbus_message_unref(message);
590 msg_init_error:
591         g_free(settings_path);
592         
593         g_slist_foreach(settings, free_settings_item, NULL);
594         g_slist_free(settings);
595         
596         g_object_unref(client);
597 }
598