Panel

/* $Id$ */
/* Copyright (c) 2011-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Panel */
/* 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, version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>. */
/* TODO:
* - implement more control events
* - make sure it works with WEP networks
* - fix the case where every network available is disabled
* - tooltip with details on the button (interface tracked...) */
#include <System.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <dirent.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>
#include "Panel/applet.h"
#define _(string) gettext(string)
#define N_(string) string
/* constants */
#ifndef PREFIX
# define PREFIX "/usr/local"
#endif
#ifndef BINDIR
# define BINDIR PREFIX "/bin"
#endif
#ifndef TMPDIR
# define TMPDIR "/tmp"
#endif
#ifndef WPA_SUPPLICANT_PATH
# define WPA_SUPPLICANT_PATH "/var/run/wpa_supplicant"
#endif
/* macros */
#define min(a, b) ((a) < (b) ? (a) : (b))
/* portability */
#ifndef SUN_LEN
# define SUN_LEN(su) sizeof(struct sockaddr_un)
#endif
/* wpa_supplicant */
/* private */
/* types */
typedef enum _WPACommand
{
WC_ADD_NETWORK = 0, /* char const * ssid */
WC_ATTACH,
WC_DETACH,
WC_DISABLE_NETWORK, /* unsigned int id */
WC_ENABLE_NETWORK, /* unsigned int id */
WC_LIST_NETWORKS,
WC_LOGON,
WC_LOGOFF,
WC_REASSOCIATE,
WC_REATTACH,
WC_RECONFIGURE,
WC_REMOVE_NETWORK, /* unsigned int id */
WC_SAVE_CONFIGURATION,
WC_SCAN,
WC_SCAN_RESULTS,
WC_SELECT_NETWORK, /* unsigned int id */
WC_SET_NETWORK, /* unsigned int id, gboolean quote,
char const * key, char const * value */
WC_SET_PASSWORD, /* unsigned int id, char const * password */
WC_STATUS,
WC_TERMINATE
} WPACommand;
typedef struct _WPAEntry
{
WPACommand command;
char * buf;
size_t buf_cnt;
/* for WC_ADD_NETWORK */
char * ssid;
uint32_t flags;
gboolean connect;
} WPAEntry;
typedef struct _WPAChannel
{
String * path;
int fd;
GIOChannel * channel;
guint rd_source;
guint wr_source;
WPAEntry * queue;
size_t queue_cnt;
} WPAChannel;
typedef struct _WPANetwork
{
unsigned int id;
char * name;
int enabled;
} WPANetwork;
typedef enum _WPAScanResult
{
WSR_UPDATED = 0,
WSR_ICON,
WSR_BSSID,
WSR_FREQUENCY,
WSR_LEVEL,
WSR_FLAGS,
WSR_SSID,
WSR_SSID_DISPLAY,
WSR_TOOLTIP,
WSR_ENABLED,
WSR_CAN_ENABLE
} WPAScanResult;
#define WSR_LAST WSR_CAN_ENABLE
#define WSR_COUNT (WSR_LAST + 1)
typedef enum _WPAScanResultFlag
{
WSRF_IBSS = 0x001,
WSRF_WEP = 0x002,
WSRF_WPA = 0x004,
WSRF_WPA2 = 0x008,
WSRF_PSK = 0x010,
WSRF_EAP = 0x020,
WSRF_CCMP = 0x040,
WSRF_TKIP = 0x080,
WSRF_PREAUTH = 0x100,
WSRF_ESS = 0x200
} WPAScanResultFlag;
typedef struct _PanelApplet
{
PanelAppletHelper * helper;
guint source;
WPAChannel channel[2];
/* configuration */
WPANetwork * networks;
size_t networks_cnt;
ssize_t networks_cur;
gboolean autosave;
/* status */
gboolean connected;
gboolean associated;
guint level;
uint32_t flags;
/* widgets */
GtkIconTheme * icontheme;
GtkWidget * widget;
GtkWidget * image;
gboolean blink;
#ifndef EMBEDDED
GtkWidget * label;
#endif
GtkTreeStore * store;
GtkWidget * pw_window;
GtkWidget * pw_entry;
unsigned int pw_id;
} WPA;
/* prototypes */
/* plug-in */
static WPA * _wpa_init(PanelAppletHelper * helper, GtkWidget ** widget);
static void _wpa_destroy(WPA * wpa);
/* accessors */
static GdkPixbuf * _wpa_get_icon(WPA * wpa, gint size, guint level,
uint32_t flags);
static WPANetwork * _wpa_get_network(WPA * wpa, unsigned int id);
static int _wpa_set_current_network(WPA * wpa, WPANetwork * network);
static void _wpa_set_status(WPA * wpa, gboolean connected, gboolean associated,
char const * network);
/* useful */
static int _wpa_error(WPA * wpa, char const * message, int ret);
static WPANetwork * _wpa_add_network(WPA * wpa, unsigned int id,
char const * name, int enabled);
static void _wpa_connect(WPA * wpa, char const * ssid, uint32_t flags);
static void _wpa_connect_network(WPA * wpa, WPANetwork * network);
static void _wpa_disconnect(WPA * wpa);
static void _wpa_rescan(WPA * wpa);
static void _wpa_ask_password(WPA * wpa, WPANetwork * network);
static void _wpa_notify(WPA * wpa, char const * message);
static int _wpa_queue(WPA * wpa, WPAChannel * channel, WPACommand command, ...);
static int _wpa_reset(WPA * wpa);
static int _wpa_start(WPA * wpa);
static void _wpa_stop(WPA * wpa);
/* callbacks */
static void _on_clicked(gpointer data);
static gboolean _on_timeout(gpointer data);
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
gpointer data);
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data);
/* public */
/* variables */
PanelAppletDefinition applet =
{
N_("Wifi"),
"network-wireless",
NULL,
_wpa_init,
_wpa_destroy,
NULL,
FALSE,
TRUE
};
/* private */
/* functions */
/* wpa_init */
static void _init_channel(WPAChannel * channel);
static WPA * _wpa_init(PanelAppletHelper * helper, GtkWidget ** widget)
{
WPA * wpa;
GtkWidget * hbox;
PangoFontDescription * bold;
char const * p;
if((wpa = object_new(sizeof(*wpa))) == NULL)
return NULL;
wpa->helper = helper;
wpa->source = 0;
_init_channel(&wpa->channel[0]);
_init_channel(&wpa->channel[1]);
wpa->networks = NULL;
wpa->networks_cnt = 0;
wpa->networks_cur = -1;
/* autosave except if explicitly disabled */
p = helper->config_get(helper->panel, "wpa_supplicant", "autosave");
wpa->autosave = (p == NULL || strtol(p, NULL, 10) != 0) ? TRUE : FALSE;
wpa->connected = FALSE;
wpa->associated = FALSE;
wpa->level = 0;
wpa->flags = 0;
/* widgets */
wpa->icontheme = gtk_icon_theme_get_default();
bold = pango_font_description_new();
pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD);
#if GTK_CHECK_VERSION(3, 0, 0)
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
hbox = gtk_hbox_new(FALSE, 4);
#endif
wpa->image = gtk_image_new();
wpa->blink = FALSE;
gtk_box_pack_start(GTK_BOX(hbox), wpa->image, FALSE, TRUE, 0);
#ifndef EMBEDDED
wpa->label = gtk_label_new(" ");
# if GTK_CHECK_VERSION(3, 0, 0)
gtk_widget_override_font(wpa->label, bold);
# else
gtk_widget_modify_font(wpa->label, bold);
#endif
gtk_box_pack_start(GTK_BOX(hbox), wpa->label, FALSE, TRUE, 0);
#endif
wpa->store = gtk_tree_store_new(WSR_COUNT, G_TYPE_BOOLEAN,
GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT,
G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
_wpa_start(wpa);
gtk_widget_show_all(hbox);
pango_font_description_free(bold);
if(helper->window == NULL
|| panel_window_get_type(helper->window)
== PANEL_WINDOW_TYPE_NOTIFICATION)
wpa->widget = hbox;
else
{
wpa->widget = gtk_button_new();
gtk_button_set_relief(GTK_BUTTON(wpa->widget), GTK_RELIEF_NONE);
#if GTK_CHECK_VERSION(2, 12, 0)
gtk_widget_set_tooltip_text(wpa->widget,
_("Wireless networking"));
#endif
g_signal_connect_swapped(wpa->widget, "clicked", G_CALLBACK(
_on_clicked), wpa);
gtk_container_add(GTK_CONTAINER(wpa->widget), hbox);
}
wpa->pw_window = NULL;
wpa->pw_id = 0;
_wpa_set_status(wpa, FALSE, FALSE, _("Unavailable"));
*widget = wpa->widget;
return wpa;
}
static void _init_channel(WPAChannel * channel)
{
channel->path = NULL;
channel->fd = -1;
channel->channel = NULL;
channel->rd_source = 0;
channel->wr_source = 0;
channel->queue = NULL;
channel->queue_cnt = 0;
}
/* wpa_destroy */
static void _wpa_destroy(WPA * wpa)
{
_wpa_stop(wpa);
if(wpa->pw_window != NULL)
gtk_widget_destroy(wpa->pw_window);
gtk_widget_destroy(wpa->widget);
object_delete(wpa);
}
/* accessors */
/* wpa_get_icon */
static GdkPixbuf * _wpa_get_icon(WPA * wpa, gint size, guint level,
uint32_t flags)
{
GdkPixbuf * ret;
char const * name;
#if GTK_CHECK_VERSION(2, 14, 0)
const int f = GTK_ICON_LOOKUP_USE_BUILTIN
| GTK_ICON_LOOKUP_FORCE_SIZE;
#else
const int f = GTK_ICON_LOOKUP_USE_BUILTIN;
#endif
GdkPixbuf * pixbuf;
char const * emblem = (flags & WSRF_WPA2) ? "security-high"
: ((flags & WSRF_WPA) ? "security-medium"
: ((flags & WSRF_WEP) ? "security-low" : NULL));
/* FIXME check if the mapping is right (and use our own icons) */
if(flags & WSRF_IBSS)
name = "nm-adhoc";
else if(level >= 200)
name = "phone-signal-100";
else if(level >= 150)
name = "phone-signal-75";
else if(level >= 100)
name = "phone-signal-50";
else if(level >= 50)
name = "phone-signal-25";
else
name = "phone-signal-00";
if((pixbuf = gtk_icon_theme_load_icon(wpa->icontheme, name, size, 0,
NULL)) == NULL)
return NULL;
if((ret = gdk_pixbuf_copy(pixbuf)) == NULL)
return pixbuf;
g_object_unref(pixbuf);
size = min(24, size / 2);
if(emblem == NULL)
return ret;
pixbuf = gtk_icon_theme_load_icon(wpa->icontheme, emblem, size, f,
NULL);
if(pixbuf != NULL)
{
gdk_pixbuf_composite(pixbuf, ret, 0, 0, size, size, 0, 0, 1.0,
1.0, GDK_INTERP_NEAREST, 255);
g_object_unref(pixbuf);
}
return ret;
}
/* wpa_get_network */
static WPANetwork * _wpa_get_network(WPA * wpa, unsigned int id)
{
size_t i;
for(i = 0; i < wpa->networks_cnt; i++)
if(wpa->networks[i].id == id)
return &wpa->networks[i];
return NULL;
}
/* wpa_set_current_network */
static int _wpa_set_current_network(WPA * wpa, WPANetwork * network)
{
size_t i;
for(i = 0; i < wpa->networks_cnt; i++)
if(wpa->networks[i].id == network->id)
{
wpa->networks_cur = i;
return 0;
}
return -1;
}
/* wpa_set_status */
static void _wpa_set_status(WPA * wpa, gboolean connected, gboolean associated,
char const * network)
{
GtkIconSize iconsize;
char const * icon;
gint size = 16;
GdkPixbuf * pixbuf = NULL;
char buf[80];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u, %u, \"%s\")\n", __func__, connected,
associated, network);
#endif
iconsize = (wpa->helper->window != NULL)
? panel_window_get_icon_size(wpa->helper->window)
: GTK_ICON_SIZE_SMALL_TOOLBAR;
gtk_icon_size_lookup(iconsize, &size, &size);
#ifndef EMBEDDED
gtk_widget_set_sensitive(wpa->widget, connected);
#else
if(connected)
gtk_widget_show(wpa->widget);
else
gtk_widget_hide(wpa->widget);
#endif
if(connected == FALSE && network == NULL)
{
/* an error occurred */
icon = "network-error";
network = _("Error");
}
else if(connected == FALSE && associated == FALSE)
/* not connected to wpa_supplicant */
icon = "network-offline";
else if(connected == FALSE && associated == TRUE)
{
/* connected to an interface */
icon = "network-offline";
if(wpa->connected != connected && network != NULL)
{
snprintf(buf, sizeof(buf),
_("Connected to interface %s"),
network);
_wpa_notify(wpa, buf);
}
network = (network != NULL) ? network : _("Connecting...");
}
else if(associated == FALSE)
{
/* connected but not associated */
icon = wpa->blink ? "network-idle"
: "network-transmit-receive";
wpa->blink = wpa->blink ? FALSE : TRUE;
network = (network != NULL) ? network : _("Scanning...");
}
else
{
/* connected and associated */
icon = "network-idle";
/* XXX use real values */
pixbuf = _wpa_get_icon(wpa, size, wpa->level, wpa->flags);
if(wpa->associated != associated && network != NULL)
{
snprintf(buf, sizeof(buf), _("Connected to network %s"),
network);
_wpa_notify(wpa, buf);
}
}
#if GTK_CHECK_VERSION(2, 6, 0)
if(pixbuf != NULL)
{
gtk_image_set_from_pixbuf(GTK_IMAGE(wpa->image), pixbuf);
g_object_unref(pixbuf);
}
else
gtk_image_set_from_icon_name(GTK_IMAGE(wpa->image), icon,
iconsize);
#else
if(pixbuf != NULL || (pixbuf = gtk_icon_theme_load_icon(wpa->icontheme,
icon, size, 0, NULL)) != NULL)
{
gtk_image_set_from_pixbuf(GTK_IMAGE(wpa->image), pixbuf);
g_object_unref(pixbuf);
}
else
gtk_image_set_from_stock(GTK_IMAGE(wpa->image),
(connected && associated)
? GTK_STOCK_CONNECT : GTK_STOCK_DISCONNECT,
iconsize);
#endif
#ifndef EMBEDDED
gtk_label_set_text(GTK_LABEL(wpa->label), network);
#endif
wpa->connected = connected;
wpa->associated = associated;
}
/* useful */
/* wpa_error */
static int _wpa_error(WPA * wpa, char const * message, int ret)
{
error_set("%s: %s", applet.name, message);
_wpa_set_status(wpa, FALSE, FALSE, NULL);
return wpa->helper->error(NULL, error_get(NULL), ret);
}
/* wpa_ask_password */
static void _ask_password_window(WPA * wpa);
/* callbacks */
static gboolean _ask_password_on_closex(gpointer data);
static void _ask_password_on_response(GtkWidget * widget, gint response,
gpointer data);
static void _ask_password_on_show(GtkWidget * widget, gpointer data);
static void _wpa_ask_password(WPA * wpa, WPANetwork * network)
{
if(wpa->pw_window == NULL)
_ask_password_window(wpa);
#if GTK_CHECK_VERSION(2, 6, 0)
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(
wpa->pw_window),
#else
/* FIXME wrong */
gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(wpa->pw_window),
#endif
_("The network \"%s\" is protected by a key."),
network->name);
/* reset the text if the network has changed */
if(wpa->pw_id != network->id)
gtk_entry_set_text(GTK_ENTRY(wpa->pw_entry), "");
wpa->pw_id = network->id;
gtk_window_present(GTK_WINDOW(wpa->pw_window));
}
static void _ask_password_window(WPA * wpa)
{
GtkWidget * dialog;
GtkWidget * vbox;
GtkWidget * hbox;
GtkWidget * widget;
dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
GTK_BUTTONS_OK_CANCEL, "%s", _("Password required"));
wpa->pw_window = dialog;
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
#if GTK_CHECK_VERSION(2, 10, 0)
wpa->pw_entry = gtk_image_new_from_icon_name("dialog-password",
GTK_ICON_SIZE_DIALOG);
gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog), wpa->pw_entry);
gtk_widget_show(wpa->pw_entry);
#endif
#if GTK_CHECK_VERSION(2, 6, 0)
gtk_window_set_icon_name(GTK_WINDOW(dialog), "dialog-password");
#endif
gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
gtk_window_set_title(GTK_WINDOW(dialog), _("Wireless key"));
#if GTK_CHECK_VERSION(2, 14, 0)
vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
#else
vbox = GTK_DIALOG(dialog)->vbox;
#endif
gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
#if GTK_CHECK_VERSION(3, 0, 0)
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
hbox = gtk_hbox_new(FALSE, 4);
#endif
widget = gtk_label_new(_("Key: "));
gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
wpa->pw_entry = gtk_entry_new();
gtk_entry_set_activates_default(GTK_ENTRY(wpa->pw_entry), TRUE);
gtk_entry_set_visibility(GTK_ENTRY(wpa->pw_entry), FALSE);
gtk_box_pack_start(GTK_BOX(hbox), wpa->pw_entry, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
#if GTK_CHECK_VERSION(3, 0, 0)
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
hbox = gtk_hbox_new(FALSE, 4);
#endif
widget = gtk_check_button_new_with_mnemonic(_("_Show password"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
g_signal_connect(widget, "toggled", G_CALLBACK(_ask_password_on_show),
wpa->pw_entry);
gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
gtk_widget_show_all(vbox);
g_signal_connect_swapped(wpa->pw_window, "delete-event", G_CALLBACK(
_ask_password_on_closex), wpa);
g_signal_connect(wpa->pw_window, "response", G_CALLBACK(
_ask_password_on_response), wpa);
}
static gboolean _ask_password_on_closex(gpointer data)
{
WPA * wpa = data;
gtk_widget_hide(wpa->pw_window);
return TRUE;
}
static void _ask_password_on_response(GtkWidget * widget, gint response,
gpointer data)
{
WPA * wpa = data;
char const * password;
size_t i;
WPAChannel * channel = &wpa->channel[0];
(void) widget;
if(response != GTK_RESPONSE_OK
|| (password = gtk_entry_get_text(GTK_ENTRY(
wpa->pw_entry))) == NULL)
/* enable every network again */
for(i = 0; i < wpa->networks_cnt; i++)
_wpa_queue(wpa, &wpa->channel[0], WC_ENABLE_NETWORK, i);
else
{
/* FIXME the network may have changed in the meantime */
_wpa_queue(wpa, channel, WC_SET_PASSWORD, wpa->pw_id, password);
if(wpa->autosave)
_wpa_queue(wpa, channel, WC_SAVE_CONFIGURATION);
}
gtk_widget_hide(wpa->pw_window);
}
static void _ask_password_on_show(GtkWidget * widget, gpointer data)
{
GtkWidget * entry = data;
gboolean visible;
visible = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
gtk_entry_set_visibility(GTK_ENTRY(entry), visible);
}
/* wpa_add_network */
static WPANetwork * _wpa_add_network(WPA * wpa, unsigned int id,
char const * name, int enabled)
{
WPANetwork * n;
if((n = realloc(wpa->networks, sizeof(*n) * (wpa->networks_cnt + 1)))
== NULL)
return NULL;
wpa->networks = n;
n = &wpa->networks[wpa->networks_cnt];
n->id = id;
if((n->name = strdup(name)) == NULL)
return NULL;
n->enabled = enabled;
wpa->networks_cnt++;
return n;
}
/* wpa_connect */
static void _wpa_connect(WPA * wpa, char const * ssid, uint32_t flags)
{
WPAChannel * channel = &wpa->channel[0];
size_t i;
/* check if the network is already in the list */
for(i = 0; i < wpa->networks_cnt; i++)
if(strcmp(wpa->networks[i].name, ssid) == 0)
break;
if(i < wpa->networks_cnt)
/* select this network directly */
_wpa_connect_network(wpa, &wpa->networks[i]);
else
/* add (and then select) this network */
_wpa_queue(wpa, channel, WC_ADD_NETWORK, ssid, flags, TRUE);
}
/* wpa_connect_network */
static void _wpa_connect_network(WPA * wpa, WPANetwork * network)
{
WPAChannel * channel = &wpa->channel[0];
/* select this network */
_wpa_queue(wpa, channel, WC_SELECT_NETWORK, network->id);
_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
}
/* wpa_disconnect */
static void _wpa_disconnect(WPA * wpa)
{
WPAChannel * channel = &wpa->channel[0];
size_t i;
/* enable every network again */
for(i = 0; i < wpa->networks_cnt; i++)
_wpa_queue(wpa, channel, WC_ENABLE_NETWORK, i);
_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
_wpa_rescan(wpa);
}
/* wpa_notify */
static void _wpa_notify(WPA * wpa, char const * message)
{
char const * p;
char * argv[] = { BINDIR "/panel-message", "-N", NULL, "--", NULL,
NULL };
const unsigned int flags = 0;
GError * error = NULL;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, message);
#endif
/* check if notifications are enabled */
if((p = wpa->helper->config_get(wpa->helper->panel, "wpa_supplicant",
"notify")) == NULL
|| strtol(p, NULL, 10) != 1)
return;
argv[2] = strdup(applet.icon);
argv[4] = strdup(message);
if(argv[2] == NULL || argv[4] == NULL)
wpa->helper->error(NULL, strerror(errno), 1);
else if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
== FALSE)
{
wpa->helper->error(NULL, error->message, 1);
g_error_free(error);
}
free(argv[4]);
free(argv[2]);
}
/* wpa_queue */
static char * _queue_cmd(WPACommand command, va_list ap, char const ** ssid,
uint32_t * flags, gboolean * connect);
static int _wpa_queue(WPA * wpa, WPAChannel * channel, WPACommand command, ...)
{
va_list ap;
char * cmd;
WPAEntry * p;
char const * ssid = NULL;
uint32_t flags = 0;
gboolean connect = FALSE;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(%u, ...)\n", __func__, command);
#endif
if(channel->channel == NULL)
return -1;
va_start(ap, command);
cmd = _queue_cmd(command, ap, &ssid, &flags, &connect);
va_end(ap);
if(cmd == NULL)
return -1;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, cmd);
#endif
if((p = realloc(channel->queue, sizeof(*p) * (channel->queue_cnt + 1)))
== NULL)
{
free(cmd);
return -1;
}
channel->queue = p;
p = &channel->queue[channel->queue_cnt];
p->command = command;
p->buf = cmd;
p->buf_cnt = strlen(cmd);
/* XXX may fail */
p->ssid = (ssid != NULL) ? strdup(ssid) : NULL;
p->flags = flags;
p->connect = connect;
if(channel->queue_cnt++ == 0)
channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
_on_watch_can_write, wpa);
return 0;
}
static char * _queue_cmd(WPACommand command, va_list ap, char const ** ssid,
uint32_t * flags, gboolean * connect)
{
char * cmd = NULL;
gboolean b;
char const * s;
char const * t;
unsigned int u;
switch(command)
{
case WC_ADD_NETWORK:
cmd = g_strdup_printf("ADD_NETWORK");
*ssid = va_arg(ap, char const *);
*flags = va_arg(ap, uint32_t);
*connect = va_arg(ap, gboolean);
break;
case WC_ATTACH:
cmd = strdup("ATTACH");
break;
case WC_DETACH:
cmd = strdup("DETACH");
break;
case WC_DISABLE_NETWORK:
u = va_arg(ap, unsigned int);
cmd = g_strdup_printf("DISABLE_NETWORK %u", u);
break;
case WC_ENABLE_NETWORK:
u = va_arg(ap, unsigned int);
cmd = g_strdup_printf("ENABLE_NETWORK %u", u);
break;
case WC_LIST_NETWORKS:
cmd = strdup("LIST_NETWORKS");
break;
case WC_LOGON:
cmd = strdup("LOGON");
break;
case WC_LOGOFF:
cmd = strdup("LOGOFF");
break;
case WC_REASSOCIATE:
cmd = strdup("REASSOCIATE");
break;
case WC_REATTACH:
cmd = strdup("REATTACH");
break;
case WC_RECONFIGURE:
cmd = strdup("RECONFIGURE");
break;
case WC_REMOVE_NETWORK:
u = va_arg(ap, unsigned int);
cmd = g_strdup_printf("REMOVE_NETWORK %u", u);
break;
case WC_SAVE_CONFIGURATION:
cmd = strdup("SAVE_CONFIG");
break;
case WC_SCAN:
cmd = strdup("SCAN");
break;
case WC_SCAN_RESULTS:
cmd = strdup("SCAN_RESULTS");
break;
case WC_SELECT_NETWORK:
u = va_arg(ap, unsigned int);
cmd = g_strdup_printf("SELECT_NETWORK %u", u);
break;
case WC_SET_NETWORK:
u = va_arg(ap, unsigned int);
b = va_arg(ap, gboolean);
s = va_arg(ap, char const *);
t = va_arg(ap, char const *);
cmd = g_strdup_printf("SET_NETWORK %u %s %s%s%s", u, s,
b ? "\"" : "", t, b ? "\"" : "");
break;
case WC_SET_PASSWORD:
/* FIXME really uses SET_NETWORK (and may not be psk) */
u = va_arg(ap, unsigned int);
s = va_arg(ap, char const *);
cmd = g_strdup_printf("SET_NETWORK %u psk \"%s\"", u,
s);
break;
case WC_STATUS:
cmd = strdup("STATUS-VERBOSE");
break;
case WC_TERMINATE:
cmd = strdup("TERMINATE");
break;
}
return cmd;
}
/* wpa_rescan */
static void _wpa_rescan(WPA * wpa)
{
WPAChannel * channel = &wpa->channel[0];
_wpa_queue(wpa, channel, WC_SCAN);
}
/* wpa_reset */
static int _wpa_reset(WPA * wpa)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
_wpa_stop(wpa);
return _wpa_start(wpa);
}
/* wpa_start */
static gboolean _start_timeout(gpointer data);
static int _timeout_channel(WPA * wpa, WPAChannel * channel);
static int _timeout_channel_interface(WPA * wpa, WPAChannel * channel,
char const * path, char const * interface);
static int _wpa_start(WPA * wpa)
{
if(wpa->source != 0)
g_source_remove(wpa->source);
wpa->connected = FALSE;
wpa->associated = FALSE;
/* reconnect to the daemon */
wpa->source = g_idle_add(_start_timeout, wpa);
return 0;
}
static gboolean _start_timeout(gpointer data)
{
const unsigned int timeout = 5000;
WPA * wpa = data;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(_timeout_channel(wpa, &wpa->channel[0]) != 0
|| _timeout_channel(wpa, &wpa->channel[1]) != 0)
{
_wpa_stop(wpa);
wpa->source = g_timeout_add(timeout, _start_timeout, wpa);
return FALSE;
}
_on_timeout(wpa);
wpa->source = g_timeout_add(timeout, _on_timeout, wpa);
_wpa_queue(wpa, &wpa->channel[1], WC_ATTACH);
return FALSE;
}
static int _timeout_channel(WPA * wpa, WPAChannel * channel)
{
int ret;
char const path[] = WPA_SUPPLICANT_PATH;
char const * interface;
char const * p;
DIR * dir;
struct dirent * de;
struct sockaddr_un lu;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if((p = getenv("TMPDIR")) == NULL)
p = TMPDIR;
if((channel->path = string_new_append(p, "/panel_wpa_supplicant.XXXXXX",
NULL)) == NULL)
return -wpa->helper->error(NULL, "snprintf", 1);
if(mktemp(channel->path) == NULL)
return -wpa->helper->error(NULL, "mktemp", 1);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, channel->path);
#endif
/* create the local socket */
memset(&lu, 0, sizeof(lu));
if(snprintf(lu.sun_path, sizeof(lu.sun_path), "%s", channel->path)
>= (int)sizeof(lu.sun_path))
/* XXX make sure this error is explicit enough */
return -_wpa_error(wpa, channel->path, 1);
lu.sun_family = AF_LOCAL;
if((channel->fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) == -1)
return -_wpa_error(wpa, strerror(errno), 1);
if(bind(channel->fd, (struct sockaddr *)&lu, SUN_LEN(&lu)) != 0)
return -_wpa_error(wpa, channel->path, 1);
if((interface = wpa->helper->config_get(wpa->helper->panel,
"wpa_supplicant", "interface")) != NULL)
{
ret = _timeout_channel_interface(wpa, channel, path, interface);
if(ret != 0)
wpa->helper->error(NULL, interface, 1);
}
/* look for the first available interface */
else if((dir = opendir(path)) != NULL)
{
for(ret = -1; ret != 0 && (de = readdir(dir)) != NULL;)
ret = _timeout_channel_interface(wpa, channel, path,
de->d_name);
closedir(dir);
}
else
ret = -wpa->helper->error(NULL, path, 1);
return ret;
}
static int _timeout_channel_interface(WPA * wpa, WPAChannel * channel,
char const * path, char const * interface)
{
struct stat st;
struct sockaddr_un ru;
memset(&ru, 0, sizeof(ru));
ru.sun_family = AF_UNIX;
if(snprintf(ru.sun_path, sizeof(ru.sun_path), "%s/%s", path, interface)
>= (int)sizeof(ru.sun_path)
|| lstat(ru.sun_path, &st) != 0
|| (st.st_mode & S_IFSOCK) != S_IFSOCK)
return -1;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, interface);
#endif
/* connect to the wpa_supplicant daemon */
if(connect(channel->fd, (struct sockaddr *)&ru, SUN_LEN(&ru)) != 0)
return -wpa->helper->error(NULL, "connect", 1);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() connected to %s\n", __func__, interface);
#endif
/* XXX will be done for every channel */
_wpa_set_status(wpa, FALSE, TRUE, interface);
channel->channel = g_io_channel_unix_new(channel->fd);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() %p\n", __func__, (void *)channel->channel);
#endif
g_io_channel_set_encoding(channel->channel, NULL, NULL);
g_io_channel_set_buffered(channel->channel, FALSE);
channel->rd_source = g_io_add_watch(channel->channel, G_IO_IN,
_on_watch_can_read, wpa);
return 0;
}
/* wpa_stop */
static void _stop_channel(WPA * wpa, WPAChannel * channel);
static void _wpa_stop(WPA * wpa)
{
size_t i;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
/* de-register the event source */
if(wpa->source != 0)
g_source_remove(wpa->source);
wpa->source = 0;
_stop_channel(wpa, &wpa->channel[0]);
_stop_channel(wpa, &wpa->channel[1]);
/* free the network list */
gtk_tree_store_clear(wpa->store);
for(i = 0; i < wpa->networks_cnt; i++)
free(wpa->networks[i].name);
free(wpa->networks);
wpa->networks = NULL;
wpa->networks_cnt = 0;
wpa->networks_cur = -1;
wpa->connected = FALSE;
wpa->associated = FALSE;
/* report the status */
_wpa_set_status(wpa, FALSE, FALSE, _("Unavailable"));
if(wpa->pw_window != NULL)
gtk_widget_hide(wpa->pw_window);
}
static void _stop_channel(WPA * wpa, WPAChannel * channel)
{
size_t i;
if(channel->rd_source != 0)
g_source_remove(channel->rd_source);
channel->rd_source = 0;
if(channel->wr_source != 0)
g_source_remove(channel->wr_source);
channel->wr_source = 0;
/* free the command queue */
for(i = 0; i < channel->queue_cnt; i++)
{
free(channel->queue[i].buf);
free(channel->queue[i].ssid);
}
free(channel->queue);
channel->queue = NULL;
channel->queue_cnt = 0;
/* close and remove the socket */
if(channel->channel != NULL)
{
g_io_channel_shutdown(channel->channel, TRUE, NULL);
g_io_channel_unref(channel->channel);
channel->channel = NULL;
channel->fd = -1;
}
if(channel->path != NULL)
unlink(channel->path);
if(channel->fd != -1 && close(channel->fd) != 0)
wpa->helper->error(NULL, channel->path, 1);
string_delete(channel->path);
channel->path = NULL;
channel->fd = -1;
}
/* callbacks */
/* on_clicked */
static void _clicked_available(WPA * wpa, GtkWidget * menu);
static void _clicked_network_view(WPA * wpa, GtkWidget * menu);
static void _clicked_position_menu(GtkMenu * menu, gint * x, gint * y,
gboolean * push_in, gpointer data);
static void _clicked_preferences(WPA * wpa, GtkWidget * menu);
/* callbacks */
static void _clicked_on_disconnect(gpointer data);
static void _clicked_on_network_activated(GtkWidget * widget, gpointer data);
static void _clicked_on_preferences(gpointer data);
static void _clicked_on_reassociate(gpointer data);
static void _clicked_on_rescan(gpointer data);
static void _on_clicked(gpointer data)
{
WPA * wpa = data;
GtkWidget * menu;
menu = gtk_menu_new();
_clicked_preferences(wpa, menu);
if(wpa->channel[0].fd >= 0)
_clicked_available(wpa, menu);
gtk_widget_show_all(menu);
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, _clicked_position_menu,
wpa, 0, gtk_get_current_event_time());
}
static void _clicked_available(WPA * wpa, GtkWidget * menu)
{
GtkWidget * menuitem;
GtkWidget * image;
menuitem = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
if(wpa->networks_cur >= 0)
{
/* reassociate */
menuitem = gtk_image_menu_item_new_with_label(_("Reassociate"));
#if GTK_CHECK_VERSION(2, 12, 0)
# if GTK_CHECK_VERSION(3, 10, 0)
image = gtk_image_new_from_icon_name(GTK_STOCK_DISCARD,
GTK_ICON_SIZE_MENU);
# else
image = gtk_image_new_from_stock(GTK_STOCK_DISCARD,
GTK_ICON_SIZE_MENU);
# endif
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
image);
#endif
g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
_clicked_on_reassociate), wpa);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
/* disconnect */
menuitem = gtk_image_menu_item_new_with_label(_("Disconnect"));
#if GTK_CHECK_VERSION(3, 10, 0)
image = gtk_image_new_from_icon_name(
#else
image = gtk_image_new_from_stock(
#endif
GTK_STOCK_DISCONNECT, GTK_ICON_SIZE_MENU);
g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
_clicked_on_disconnect), wpa);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
}
/* rescan */
menuitem = gtk_image_menu_item_new_with_label(_("Rescan"));
#if GTK_CHECK_VERSION(3, 10, 0)
image = gtk_image_new_from_icon_name(
#else
image = gtk_image_new_from_stock(
#endif
GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
_clicked_on_rescan), wpa);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
/* view */
_clicked_network_view(wpa, menu);
}
static void _clicked_network_view(WPA * wpa, GtkWidget * menu)
{
GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
GtkWidget * menuitem;
GtkWidget * image;
GtkTreeIter iter;
gboolean valid;
GdkPixbuf * pixbuf;
guint frequency;
guint level;
guint flags;
gchar * dssid;
#if GTK_CHECK_VERSION(2, 12, 0)
gchar * tooltip;
#endif
GtkTreePath * path;
GtkTreeRowReference * row;
if((valid = gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
return;
menuitem = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
for(; valid == TRUE; valid = gtk_tree_model_iter_next(model, &iter))
{
gtk_tree_model_get(model, &iter, WSR_ICON, &pixbuf,
WSR_FREQUENCY, &frequency, WSR_LEVEL, &level,
WSR_FLAGS, &flags, WSR_SSID_DISPLAY, &dssid,
#if GTK_CHECK_VERSION(2, 12, 0)
WSR_TOOLTIP, &tooltip,
#endif
-1);
menuitem = gtk_image_menu_item_new_with_label(dssid);
#if GTK_CHECK_VERSION(2, 12, 0)
gtk_widget_set_tooltip_text(menuitem, tooltip);
g_free(tooltip);
#endif
path = gtk_tree_model_get_path(model, &iter);
row = gtk_tree_row_reference_new(model, path);
gtk_tree_path_free(path);
g_object_set_data(G_OBJECT(menuitem), "row", row);
image = gtk_image_new_from_pixbuf(pixbuf);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
image);
g_signal_connect(menuitem, "activate", G_CALLBACK(
_clicked_on_network_activated), wpa);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
g_free(dssid);
#if 0 /* XXX memory leak (for g_object_set_data() above) */
gtk_tree_row_reference_free(treeref);
#endif
}
}
static void _clicked_preferences(WPA * wpa, GtkWidget * menu)
{
GtkWidget * menuitem;
#if GTK_CHECK_VERSION(3, 10, 0)
menuitem = gtk_image_menu_item_new_with_label(_("Preferences"));
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
gtk_image_new_from_icon_name(GTK_STOCK_PREFERENCES,
GTK_ICON_SIZE_MENU));
#else
menuitem = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES,
NULL);
#endif
g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
_clicked_on_preferences), wpa);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
}
/* callbacks */
static void _clicked_on_disconnect(gpointer data)
{
WPA * wpa = data;
_wpa_disconnect(wpa);
}
static void _clicked_on_network_activated(GtkWidget * widget, gpointer data)
{
WPA * wpa = data;
GtkTreeRowReference * row;
GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
GtkTreePath * path;
GtkTreeIter iter;
gchar * ssid;
guint f;
if((row = g_object_get_data(G_OBJECT(widget), "row")) == NULL)
/* FIXME implement */
return;
if((path = gtk_tree_row_reference_get_path(row)) == NULL
|| gtk_tree_model_get_iter(model, &iter, path) != TRUE)
{
gtk_tree_row_reference_free(row);
return;
}
gtk_tree_model_get(model, &iter, WSR_SSID, &ssid, WSR_FLAGS, &f, -1);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, ssid);
#endif
_wpa_connect(wpa, ssid, f);
g_free(ssid);
#if 1 /* XXX partly remediate memory leak (see above) */
gtk_tree_row_reference_free(row);
#endif
}
static void _clicked_on_preferences(gpointer data)
{
WPA * wpa = data;
char * argv[] = { BINDIR "/wifibrowser", NULL };
const unsigned int flags = 0;
GError * error = NULL;
if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
== FALSE)
{
wpa->helper->error(NULL, error->message, 1);
g_error_free(error);
}
}
static void _clicked_on_reassociate(gpointer data)
{
WPA * wpa = data;
WPAChannel * channel = &wpa->channel[0];
_wpa_queue(wpa, channel, WC_REASSOCIATE);
}
static void _clicked_on_rescan(gpointer data)
{
WPA * wpa = data;
_wpa_rescan(wpa);
}
static void _clicked_position_menu(GtkMenu * menu, gint * x, gint * y,
gboolean * push_in, gpointer data)
{
WPA * wpa = data;
GtkAllocation a;
#if GTK_CHECK_VERSION(2, 18, 0)
gtk_widget_get_allocation(wpa->widget, &a);
#else
a = wpa->widget->allocation;
#endif
*x = a.x;
*y = a.y;
wpa->helper->position_menu(wpa->helper->panel, menu, x, y, push_in);
}
/* on_timeout */
static gboolean _on_timeout(gpointer data)
{
WPA * wpa = data;
WPAChannel * channel = &wpa->channel[0];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(wpa->networks == NULL)
{
_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
_wpa_queue(wpa, channel, WC_SCAN_RESULTS);
}
_wpa_queue(wpa, channel, WC_STATUS);
return TRUE;
}
/* on_watch_can_read */
static void _read_add_network(WPA * wpa, WPAChannel * channel, char const * buf,
size_t cnt, char const * ssid, uint32_t flags,
gboolean connect);
static WPAChannel * _read_channel(WPA * wpa, GIOChannel * source);
static void _read_event_ctrl(WPA * wpa, char const * event);
static void _read_event_wpa(WPA * wpa, char const * event);
static void _read_list_networks(WPA * wpa, char const * buf, size_t cnt);
static void _read_scan_results(WPA * wpa, char const * buf, size_t cnt);
static void _read_scan_results_cleanup(WPA * wpa, GtkTreeModel * model);
static char const * _read_scan_results_flag(WPA * wpa, char const * p,
uint32_t * ret);
static uint32_t _read_scan_results_flags(WPA * wpa, char const * flags);
static void _read_scan_results_iter(WPA * wpa, GtkTreeIter * iter,
char const * bssid);
static void _read_scan_results_iter_ssid(WPA * wpa, GtkTreeIter * iter,
char const * bssid, char const * ssid, unsigned int level,
unsigned int frequency, unsigned int flags, GdkPixbuf * pixbuf,
char const * tooltip);
static void _read_scan_results_reset(WPA * wpa, GtkTreeModel * model);
static void _read_scan_results_tooltip(char * buf, size_t buf_cnt,
unsigned int frequency, unsigned int level, uint32_t flags);
static void _read_status(WPA * wpa, char const * buf, size_t cnt);
static void _read_unsolicited(WPA * wpa, char const * buf, size_t cnt);
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
gpointer data)
{
WPA * wpa = data;
WPAChannel * channel;
WPAEntry * entry;
char buf[4096]; /* XXX in wpa */
gsize cnt;
GError * error = NULL;
GIOStatus status;
char const * p;
if(condition != G_IO_IN)
return FALSE; /* should not happen */
if((channel = _read_channel(wpa, source)) == NULL)
return FALSE; /* should not happen */
entry = (channel->queue_cnt > 0) ? &channel->queue[0] : NULL;
status = g_io_channel_read_chars(source, buf, sizeof(buf), &cnt,
&error);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"", __func__);
fwrite(buf, sizeof(*buf), cnt, stderr);
fprintf(stderr, "\"\n");
#endif
switch(status)
{
case G_IO_STATUS_NORMAL:
if(entry == NULL)
{
_read_unsolicited(wpa, buf, cnt);
break;
}
if(cnt == 3 && strncmp(buf, "OK\n", cnt) == 0)
break;
if(cnt == 5 && strncmp(buf, "FAIL\n", cnt) == 0)
{
/* FIXME improve the error message */
p = _("Unknown error");
if(entry->command == WC_SAVE_CONFIGURATION)
p = _("Could not save the"
" configuration");
wpa->helper->error(NULL, p, 0);
break;
}
if(entry->command == WC_ADD_NETWORK)
_read_add_network(wpa, channel, buf, cnt,
entry->ssid, entry->flags,
entry->connect);
else if(entry->command == WC_LIST_NETWORKS)
_read_list_networks(wpa, buf, cnt);
else if(entry->command == WC_SCAN_RESULTS)
_read_scan_results(wpa, buf, cnt);
else if(entry->command == WC_STATUS)
_read_status(wpa, buf, cnt);
break;
case G_IO_STATUS_ERROR:
_wpa_error(wpa, error->message, 1);
g_error_free(error);
/* fallthrough */
case G_IO_STATUS_EOF:
default: /* should not happen */
_wpa_reset(wpa);
return FALSE;
}
if(entry != NULL)
{
free(channel->queue[0].ssid);
memmove(&channel->queue[0], &channel->queue[1],
sizeof(channel->queue[0])
* (--channel->queue_cnt));
}
/* schedule commands again */
if(channel->queue_cnt > 0 && channel->wr_source == 0)
channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
_on_watch_can_write, wpa);
return TRUE;
}
static void _read_add_network(WPA * wpa, WPAChannel * channel, char const * buf,
size_t cnt, char const * ssid, uint32_t flags, gboolean connect)
{
unsigned int id;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\", 0x%08x)\n", __func__, ssid, flags);
#endif
if(cnt < 2 || buf[cnt - 1] != '\n')
return;
errno = 0;
id = strtoul(buf, NULL, 10);
if(buf[0] == '\0' || errno != 0)
return;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() %u \"%s\"\n", __func__, id, ssid);
#endif
_wpa_queue(wpa, channel, WC_SET_NETWORK, id, TRUE, "ssid", ssid);
if((flags & (WSRF_WPA | WSRF_WPA2)) != 0)
_wpa_queue(wpa, channel, WC_SET_NETWORK, id, FALSE, "key_mgmt",
"WPA-PSK");
else
/* required to be able to connect to open or WEP networks */
_wpa_queue(wpa, channel, WC_SET_NETWORK, id, FALSE, "key_mgmt",
"NONE");
if(wpa->autosave)
_wpa_queue(wpa, channel, WC_SAVE_CONFIGURATION);
if(connect)
{
_wpa_queue(wpa, channel, WC_SELECT_NETWORK, id);
_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
}
}
static WPAChannel * _read_channel(WPA * wpa, GIOChannel * source)
{
if(source == wpa->channel[0].channel
&& wpa->channel[0].queue_cnt > 0
&& wpa->channel[0].queue[0].buf_cnt == 0)
return &wpa->channel[0];
else if(source == wpa->channel[1].channel)
return &wpa->channel[1];
return NULL;
}
static void _read_event_ctrl(WPA * wpa, char const * event)
{
char const password_changed[] = "PASSWORD-CHANGED ";
char const scan_results[] = "SCAN-RESULTS";
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, event);
#endif
if(strncmp(event, password_changed, sizeof(password_changed) - 1) == 0)
_wpa_notify(wpa, &event[sizeof(password_changed)]);
else if(strncmp(event, password_changed, sizeof(password_changed) - 2)
== 0)
_wpa_notify(wpa, _("Password changed"));
else if(strncmp(event, scan_results, sizeof(scan_results) - 1) == 0)
_wpa_queue(wpa, &wpa->channel[0], WC_SCAN_RESULTS);
#ifdef DEBUG
else
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, event);
#endif
}
static void _read_event_wpa(WPA * wpa, char const * event)
{
char const handshake[] = "4-Way Handshake failed"
" - pre-shared key may be incorrect";
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
/* XXX hackish, blame wpa_supplicant(8) */
if(strcmp(event, handshake) == 0 && wpa->networks_cur >= 0)
/* FIXME does not work if networks_cur is not set */
_wpa_ask_password(wpa, &wpa->networks[wpa->networks_cur]);
}
static void _read_list_networks(WPA * wpa, char const * buf, size_t cnt)
{
WPANetwork * n;
size_t i;
size_t j;
char * p = NULL;
char * q;
unsigned int u;
char ssid[80];
char bssid[80];
char flags[80];
int res;
for(i = 0; i < wpa->networks_cnt; i++)
free(wpa->networks[i].name);
free(wpa->networks);
wpa->networks = NULL;
wpa->networks_cnt = 0;
wpa->networks_cur = -1;
for(i = 0; i < cnt; i = j)
{
for(j = i; j < cnt; j++)
if(buf[j] == '\n')
break;
if((q = realloc(p, ++j - i)) == NULL)
continue;
p = q;
snprintf(p, j - i, "%s", &buf[i]);
p[j - i - 1] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
if((res = sscanf(p, "%u %79[^\t] %79[^\t] [%79[^]]]", &u, ssid,
bssid, flags)) >= 3)
{
ssid[sizeof(ssid) - 1] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, ssid);
#endif
/* FIXME store the scan results instead */
if((n = _wpa_add_network(wpa, u, ssid, 1)) == NULL)
continue;
if(res >= 4 && strcmp(flags, "DISABLED") == 0)
n->enabled = 0;
else if(res >= 4 && strcmp(flags, "CURRENT") == 0)
{
_wpa_set_current_network(wpa, n);
_wpa_queue(wpa, &wpa->channel[0], WC_STATUS);
}
}
}
free(p);
if(wpa->networks_cur < 0)
{
/* determine if only one network is enabled */
for(i = 0, j = 0; i < wpa->networks_cnt; i++)
if(wpa->networks[i].enabled)
j++;
if(wpa->networks_cnt > 1 && j == 1)
for(i = 0, j = 0; i < wpa->networks_cnt; i++)
if(wpa->networks[i].enabled)
{
/* set as the current network */
wpa->networks_cur = i;
break;
}
}
}
static void _read_scan_results(WPA * wpa, char const * buf, size_t cnt)
{
GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
gint size = 16;
size_t i;
size_t j;
char * p = NULL;
char * q;
int res;
GdkPixbuf * pixbuf;
char bssid[18];
unsigned int frequency;
unsigned int level;
char flags[80];
uint32_t f;
char ssid[80];
char tooltip[80];
GtkTreeIter iter;
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &size, &size);
_read_scan_results_reset(wpa, model);
for(i = 0; i < cnt; i = j)
{
for(j = i; j < cnt; j++)
if(buf[j] == '\n')
break;
if((q = realloc(p, ++j - i)) == NULL)
continue;
p = q;
snprintf(p, j - i, "%s", &buf[i]);
p[j - i - 1] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
if((res = sscanf(p, "%17s %u %u %79s %79[^\n]", bssid,
&frequency, &level, flags,
ssid)) >= 3)
{
bssid[sizeof(bssid) - 1] = '\0';
flags[sizeof(flags) - 1] = '\0';
ssid[sizeof(ssid) - 1] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"%s\" %u %u %s %s\n",
__func__, bssid, frequency, level,
flags, ssid);
#endif
f = _read_scan_results_flags(wpa, flags);
pixbuf = _wpa_get_icon(wpa, size, level, f);
_read_scan_results_tooltip(tooltip, sizeof(tooltip),
frequency, level, f);
if(res == 5)
{
_read_scan_results_iter_ssid(wpa, &iter, bssid,
ssid, level, frequency, f,
pixbuf, tooltip);
q = ssid;
}
else
{
_read_scan_results_iter(wpa, &iter, bssid);
snprintf(ssid, sizeof(ssid), _("Hidden (%s)"),
bssid);
q = "";;
}
gtk_tree_store_set(wpa->store, &iter, WSR_UPDATED, TRUE,
WSR_ICON, pixbuf, WSR_BSSID, bssid,
WSR_FREQUENCY, frequency,
WSR_LEVEL, level, WSR_FLAGS, f,
WSR_TOOLTIP, tooltip, WSR_SSID, q,
WSR_SSID_DISPLAY, ssid, -1);
}
}
free(p);
_read_scan_results_cleanup(wpa, model);
}
static void _read_scan_results_cleanup(WPA * wpa, GtkTreeModel * model)
{
GtkTreeIter iter;
GtkTreeIter child;
gboolean valid;
/* remove the outdated entries */
for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;)
{
gtk_tree_model_get(model, &iter, WSR_UPDATED, &valid, -1);
if(valid == FALSE)
{
valid = gtk_tree_store_remove(wpa->store, &iter);
continue;
}
for(valid = gtk_tree_model_iter_children(model, &child, &iter);
valid == TRUE;)
{
gtk_tree_model_get(model, &child, WSR_UPDATED, &valid,
-1);
if(valid == FALSE)
valid = gtk_tree_store_remove(wpa->store,
&child);
else
valid = gtk_tree_model_iter_next(model, &child);
}
valid = gtk_tree_model_iter_next(model, &iter);
}
}
static uint32_t _read_scan_results_flags(WPA * wpa, char const * flags)
{
uint32_t ret = 0;
char const * p;
for(p = flags; *p != '\0';)
if(*(p++) == '[')
{
p = _read_scan_results_flag(wpa, p, &ret);
for(p++; *p != '\0' && *p != ']'; p++);
}
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() => 0x%x\n", __func__, ret);
#endif
return ret;
}
static char const * _read_scan_results_flag(WPA * wpa, char const * p,
uint32_t * ret)
{
char const wep[] = "WEP";
char const wep104[] = "WEP104";
char const wpa1[] = "WPA-";
char const wpa2[] = "WPA2-";
char const psk[] = "PSK";
char const eap[] = "EAP";
char const ccmp[] = "CCMP";
char const tkipccmp[] = "TKIP+CCMP";
char const tkip[] = "TKIP";
char const preauth[] = "preauth";
char const ess[] = "ESS";
char const ibss[] = "IBSS";
(void) wpa;
/* FIXME parse more consistently */
if(strncmp(wep, p, sizeof(wep) - 1) == 0)
{
*ret |= WSRF_WEP;
p += sizeof(wep) - 1;
}
else if(strncmp(wpa1, p, sizeof(wpa1) - 1) == 0)
{
*ret |= WSRF_WPA;
p += sizeof(wpa1) - 1;
}
else if(strncmp(wpa2, p, sizeof(wpa2) - 1) == 0)
{
*ret |= WSRF_WPA2;
p += sizeof(wpa2) - 1;
}
else if(strncmp(ess, p, sizeof(ess) - 1) == 0)
{
*ret |= WSRF_ESS;
p += sizeof(ess) - 1;
}
else if(strncmp(ibss, p, sizeof(ibss) - 1) == 0)
{
*ret |= WSRF_IBSS;
p += sizeof(ibss) - 1;
}
else
return p;
if(*p == '-')
p++;
if(strncmp(psk, p, sizeof(psk) - 1) == 0)
{
*ret |= WSRF_PSK;
p += sizeof(psk) - 1;
}
else if(strncmp(eap, p, sizeof(eap) - 1) == 0)
{
*ret |= WSRF_EAP;
p += sizeof(eap) - 1;
}
if(*p == '-')
p++;
if(strncmp(ccmp, p, sizeof(ccmp) - 1) == 0)
{
*ret |= WSRF_CCMP;
p += sizeof(ccmp) - 1;
}
else if(strncmp(tkipccmp, p, sizeof(tkipccmp) - 1) == 0)
{
*ret |= WSRF_TKIP | WSRF_CCMP;
p += sizeof(tkipccmp) - 1;
}
else if(strncmp(tkip, p, sizeof(tkip) - 1) == 0)
{
*ret |= WSRF_TKIP;
p += sizeof(tkip) - 1;
}
else if(strncmp(wep104, p, sizeof(wep104) - 1) == 0)
{
*ret &= ~(WSRF_IBSS | WSRF_WPA | WSRF_WPA2);
*ret |= WSRF_WEP;
p += sizeof(wep104) - 1;
}
else
return p;
if(*p == '-')
p++;
if(strncmp(preauth, p, sizeof(preauth) - 1) == 0)
{
*ret |= WSRF_PREAUTH;
p += sizeof(preauth) - 1;
}
else
return p;
/* FIXME implement more */
return p;
}
static void _read_scan_results_iter(WPA * wpa, GtkTreeIter * iter,
char const * bssid)
{
GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
gboolean valid;
gchar * b;
int res;
for(valid = gtk_tree_model_get_iter_first(model, iter); valid == TRUE;
valid = gtk_tree_model_iter_next(model, iter))
{
gtk_tree_model_get(model, iter, WSR_BSSID, &b, -1);
res = (b != NULL && strcmp(b, bssid) == 0) ? 0 : -1;
g_free(b);
if(res == 0)
return;
}
gtk_tree_store_append(wpa->store, iter, NULL);
}
static void _read_scan_results_iter_ssid(WPA * wpa, GtkTreeIter * iter,
char const * bssid, char const * ssid, unsigned int level,
unsigned int frequency, unsigned int flags, GdkPixbuf * pixbuf,
char const * tooltip)
{
GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
gboolean valid;
gchar * s;
unsigned int f;
unsigned int l = 0;
int res;
GtkTreeIter parent;
/* look for the network in the list */
for(valid = gtk_tree_model_get_iter_first(model, iter); valid == TRUE;
valid = gtk_tree_model_iter_next(model, iter))
{
gtk_tree_model_get(model, iter, WSR_SSID, &s, WSR_LEVEL, &l,
WSR_FLAGS, &f, -1);
res = (s != NULL && strcmp(s, ssid) == 0 && f == flags)
? 0 : -1;
g_free(s);
if(res == 0)
break;
}
if(valid != TRUE)
{
gtk_tree_store_append(wpa->store, iter, NULL);
/* FIXME determine the true value for WSR_ENABLED */
gtk_tree_store_set(wpa->store, iter, WSR_UPDATED, TRUE,
WSR_LEVEL, level, WSR_FREQUENCY, frequency,
WSR_FLAGS, flags, WSR_SSID, ssid,
WSR_SSID_DISPLAY, ssid, WSR_ICON, pixbuf,
WSR_TOOLTIP, tooltip, WSR_ENABLED, FALSE,
WSR_CAN_ENABLE, TRUE, -1);
}
else
gtk_tree_store_set(wpa->store, iter, WSR_UPDATED, TRUE,
/* refresh the level and icon if relevant */
(level > l) ? WSR_LEVEL : -1, level,
WSR_FREQUENCY, frequency, WSR_ICON, pixbuf,
WSR_TOOLTIP, tooltip, WSR_ENABLED, FALSE,
WSR_CAN_ENABLE, FALSE, -1);
/* look for the BSSID in the network */
parent = *iter;
for(valid = gtk_tree_model_iter_children(model, iter, &parent);
valid == TRUE;
valid = gtk_tree_model_iter_next(model, iter))
{
gtk_tree_model_get(model, iter, WSR_BSSID, &s, -1);
res = (s != NULL && strcmp(s, bssid) == 0) ? 0 : -1;
g_free(s);
if(res == 0)
break;
}
if(valid != TRUE)
gtk_tree_store_append(wpa->store, iter, &parent);
}
static void _read_scan_results_reset(WPA * wpa, GtkTreeModel * model)
{
GtkTreeIter iter;
GtkTreeIter child;
gboolean valid;
/* mark every entry as obsolete */
for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;
valid = gtk_tree_model_iter_next(model, &iter))
{
gtk_tree_store_set(wpa->store, &iter, WSR_UPDATED, FALSE, -1);
for(valid = gtk_tree_model_iter_children(model, &child, &iter);
valid == TRUE;
valid = gtk_tree_model_iter_next(model, &child))
gtk_tree_store_set(wpa->store, &child,
WSR_UPDATED, FALSE, -1);
}
}
static void _read_scan_results_tooltip(char * buf, size_t buf_cnt,
unsigned int frequency, unsigned int level, uint32_t flags)
{
char const * security = (flags & WSRF_WPA2) ? "WPA2"
: ((flags & WSRF_WPA) ? "WPA"
: ((flags & WSRF_WEP) ? "WEP" : NULL));
/* FIXME mention the channel instead of the frequency */
snprintf(buf, buf_cnt, _("Frequency: %u\nLevel: %u%s%s"), frequency,
level, (security != NULL) ? _("\nSecurity: ") : "",
(security != NULL) ? security : "");
}
static void _read_status(WPA * wpa, char const * buf, size_t cnt)
{
gboolean associated = FALSE;
int id = -1;
char * network = NULL;
size_t i;
size_t j;
char * p = NULL;
char * q;
char variable[80];
char value[80];
WPANetwork * n;
wpa->flags = 0;
for(i = 0; i < cnt; i = j)
{
for(j = i; j < cnt; j++)
if(buf[j] == '\n')
break;
if((q = realloc(p, ++j - i)) == NULL)
continue;
p = q;
snprintf(p, j - i, "%s", &buf[i]);
p[j - i - 1] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
if(sscanf(p, "%79[^=]=%79[^\n]", variable, value) != 2)
continue;
variable[sizeof(variable) - 1] = '\0';
value[sizeof(value) - 1] = '\0';
if(strcmp(variable, "id") == 0)
{
errno = 0;
id = strtol(value, NULL, 10);
if(errno != 0 || value[0] == '\0' || id < 0)
id = -1;
}
else if(strcmp(variable, "key_mgmt") == 0)
/* XXX */
_read_scan_results_flag(wpa, value, &wpa->flags);
else if(strcmp(variable, "wpa_state") == 0)
{
if(strcmp(value, "COMPLETED") == 0)
associated = TRUE;
else if(strcmp(value, "4WAY_HANDSHAKE") == 0)
associated = FALSE;
else if(strcmp(value, "SCANNING") == 0)
associated = FALSE;
else if(strcmp(value, "INACTIVE") == 0)
associated = FALSE;
}
#ifndef EMBEDDED
else if(strcmp(variable, "ssid") == 0)
{
free(network);
/* XXX may fail */
network = strdup(value);
}
#endif
}
free(p);
/* reflect the status */
if(associated == TRUE)
{
/* no longer ask for any password */
if(wpa->pw_window != NULL)
gtk_widget_hide(wpa->pw_window);
}
_wpa_set_status(wpa, TRUE, associated, network);
free(network);
if(id >= 0 && (n = _wpa_get_network(wpa, id)) != NULL)
_wpa_set_current_network(wpa, n);
}
static void _read_unsolicited(WPA * wpa, char const * buf, size_t cnt)
{
char const ctrl_event[] = "CTRL-EVENT-";
char const wpa_event[] = "WPA: ";
size_t i;
size_t j;
char * p = NULL;
char * q;
unsigned int u;
char event[128];
for(i = 0; i < cnt; i = j)
{
for(j = i; j < cnt; j++)
if(buf[j] == '\n')
break;
if((q = realloc(p, ++j - i)) == NULL)
continue;
p = q;
snprintf(p, j - i, "%s", &buf[i]);
p[j - i - 1] = '\0';
#ifdef DEBUG
fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
if(sscanf(p, "<%u>%127[^\n]\n", &u, event) != 2)
continue;
event[sizeof(event) - 1] = '\0';
if(strncmp(event, ctrl_event, sizeof(ctrl_event) - 1) == 0)
_read_event_ctrl(wpa, event + sizeof(ctrl_event) - 1);
else if(strncmp(event, wpa_event, sizeof(wpa_event) - 1) == 0)
_read_event_wpa(wpa, event + sizeof(wpa_event) - 1);
}
free(p);
}
/* on_watch_can_write */
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
gpointer data)
{
WPA * wpa = data;
WPAChannel * channel;
WPAEntry * entry;
gsize cnt = 0;
GError * error = NULL;
GIOStatus status;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
if(condition != G_IO_OUT)
return FALSE; /* should not happen */
if(source == wpa->channel[0].channel
&& wpa->channel[0].queue_cnt > 0
&& wpa->channel[0].queue[0].buf_cnt != 0)
channel = &wpa->channel[0];
else if(source == wpa->channel[1].channel
&& wpa->channel[1].queue_cnt > 0
&& wpa->channel[1].queue[0].buf_cnt != 0)
channel = &wpa->channel[1];
else
return FALSE; /* should not happen */
entry = &channel->queue[0];
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() \"", __func__);
fwrite(entry->buf, sizeof(*entry->buf), entry->buf_cnt, stderr);
fprintf(stderr, "\"\n");
#endif
status = g_io_channel_write_chars(source, entry->buf, entry->buf_cnt,
&cnt, &error);
if(cnt != 0)
{
memmove(entry->buf, &entry->buf[cnt], entry->buf_cnt - cnt);
entry->buf_cnt -= cnt;
}
switch(status)
{
case G_IO_STATUS_NORMAL:
break;
case G_IO_STATUS_ERROR:
_wpa_error(wpa, error->message, 1);
/* fallthrough */
case G_IO_STATUS_EOF:
default: /* should not happen */
_wpa_reset(wpa);
return FALSE;
}
if(entry->buf_cnt != 0)
/* partial packet */
_wpa_reset(wpa);
else
channel->wr_source = 0;
return FALSE;
}