/* $Id: wpa_supplicant.c,v 1.17 2012/01/07 02:58:13 khorben Exp $ */ /* Copyright (c) 2011-2012 Pierre Pronchery */ /* 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 . */ /* TODO: * - configuration value for the interface to track * - more error checking * - determine if there is an asynchronous mode */ #include #include #include #include #include #include #include #include #include #include #include #include "Panel.h" /* wpa_supplicant */ /* private */ /* types */ typedef enum _WpaCommand { WC_LIST_NETWORKS, WC_STATUS } WpaCommand; typedef struct _WpaEntry { WpaCommand command; char * buf; size_t buf_cnt; } WpaEntry; typedef struct _PanelApplet { PanelAppletHelper * helper; char path[36]; guint source; int fd; GIOChannel * channel; guint rd_source; guint wr_source; WpaEntry * queue; size_t queue_cnt; /* widgets */ GtkWidget * image; GtkWidget * label; } Wpa; /* prototypes */ static Wpa * _wpa_init(PanelAppletHelper * helper, GtkWidget ** widget); static void _wpa_destroy(Wpa * wpa); static int _wpa_error(Wpa * wpa, char const * message, int ret); static int _wpa_queue(Wpa * wpa, WpaCommand command, ...); static int _wpa_reset(Wpa * wpa, gboolean connect); /* callbacks */ 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 = { "Wifi", "network-wireless", NULL, _wpa_init, _wpa_destroy, NULL, FALSE, TRUE }; /* private */ /* functions */ /* wpa_init */ static gboolean _init_timeout(gpointer data); static Wpa * _wpa_init(PanelAppletHelper * helper, GtkWidget ** widget) { Wpa * wpa; PangoFontDescription * bold; GtkWidget * hbox; if((wpa = object_new(sizeof(*wpa))) == NULL) return NULL; wpa->helper = helper; wpa->source = 0; wpa->fd = -1; wpa->channel = NULL; wpa->rd_source = 0; wpa->wr_source = 0; wpa->queue = NULL; wpa->queue_cnt = 0; /* widgets */ bold = pango_font_description_new(); pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD); hbox = gtk_hbox_new(FALSE, 4); wpa->image = gtk_image_new_from_stock(GTK_STOCK_DISCONNECT, helper->icon_size); gtk_box_pack_start(GTK_BOX(hbox), wpa->image, FALSE, TRUE, 0); wpa->label = gtk_label_new(" "); gtk_widget_modify_font(wpa->label, bold); gtk_box_pack_start(GTK_BOX(hbox), wpa->label, FALSE, TRUE, 0); if(_init_timeout(wpa) != FALSE) wpa->source = g_timeout_add(5000, _init_timeout, wpa); gtk_widget_show_all(hbox); pango_font_description_free(bold); *widget = hbox; return wpa; } static gboolean _init_timeout(gpointer data) { int ret = TRUE; Wpa * wpa = data; char const path[] = "/var/run/wpa_supplicant"; DIR * dir; struct dirent * de; struct stat st; struct sockaddr_un lu; struct sockaddr_un ru; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif snprintf(wpa->path, sizeof(wpa->path), "%s", "/tmp/panel_wpa_supplicant.XXXXXX"); if(mktemp(wpa->path) == NULL) { wpa->helper->error(NULL, "mktemp", 1); return TRUE; } #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, wpa->path); #endif if((dir = opendir(path)) == NULL) { gtk_image_set_from_stock(GTK_IMAGE(wpa->image), GTK_STOCK_DISCONNECT, wpa->helper->icon_size); gtk_label_set_text(GTK_LABEL(wpa->label), "Not running"); return wpa->helper->error(NULL, path, TRUE); } if((wpa->fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) == -1) return _wpa_error(wpa, "socket", TRUE); snprintf(lu.sun_path, sizeof(lu.sun_path), "%s", wpa->path); lu.sun_family = AF_LOCAL; if(bind(wpa->fd, (struct sockaddr *)&lu, sizeof(lu)) != 0) { close(wpa->fd); unlink(wpa->path); return _wpa_error(wpa, wpa->path, TRUE); } ru.sun_family = AF_UNIX; while((de = readdir(dir)) != NULL) { if(snprintf(ru.sun_path, sizeof(ru.sun_path), "%s/%s", path, de->d_name) >= (int)sizeof(ru.sun_path) || lstat(ru.sun_path, &st) != 0 || (st.st_mode & S_IFSOCK) != S_IFSOCK) continue; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, de->d_name); #endif if(connect(wpa->fd, (struct sockaddr *)&ru, sizeof(ru)) != 0) { wpa->helper->error(NULL, "connect", 1); continue; } #ifdef DEBUG fprintf(stderr, "DEBUG: %s() connected\n", __func__); #endif gtk_label_set_text(GTK_LABEL(wpa->label), de->d_name); wpa->channel = g_io_channel_unix_new(wpa->fd); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %p\n", __func__, (void *)wpa->channel); #endif g_io_channel_set_encoding(wpa->channel, NULL, NULL); g_io_channel_set_buffered(wpa->channel, FALSE); _on_timeout(wpa); wpa->source = g_timeout_add(5000, _on_timeout, wpa); ret = FALSE; break; } if(ret == TRUE) { close(wpa->fd); wpa->fd = -1; } closedir(dir); return ret; } /* wpa_destroy */ static void _wpa_destroy(Wpa * wpa) { _wpa_reset(wpa, FALSE); object_delete(wpa); } /* wpa_error */ static int _wpa_error(Wpa * wpa, char const * message, int ret) { gtk_image_set_from_icon_name(GTK_IMAGE(wpa->image), "error", wpa->helper->icon_size); gtk_label_set_text(GTK_LABEL(wpa->label), "Error"); return wpa->helper->error(NULL, message, ret); } /* wpa_queue */ static int _wpa_queue(Wpa * wpa, WpaCommand command, ...) { char const * cmd = NULL; WpaEntry * p; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%u, ...)\n", __func__, command); #endif if(wpa->channel == NULL) return -1; switch(command) { case WC_LIST_NETWORKS: cmd = "LIST_NETWORKS"; break; case WC_STATUS: cmd = "STATUS-VERBOSE"; break; } if(cmd == NULL) return -1; if((p = realloc(wpa->queue, sizeof(*p) * (wpa->queue_cnt + 1))) == NULL) return -1; wpa->queue = p; p = &wpa->queue[wpa->queue_cnt]; p->command = command; p->buf = strdup(cmd); p->buf_cnt = strlen(cmd); if(p->buf == NULL) return -1; if(wpa->queue_cnt++ == 0) wpa->wr_source = g_io_add_watch(wpa->channel, G_IO_OUT, _on_watch_can_write, wpa); return 0; } /* wpa_reset */ static int _wpa_reset(Wpa * wpa, gboolean connect) { size_t i; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(wpa->source != 0) g_source_remove(wpa->source); wpa->source = 0; if(wpa->rd_source != 0) g_source_remove(wpa->rd_source); wpa->rd_source = 0; if(wpa->wr_source != 0) g_source_remove(wpa->wr_source); wpa->wr_source = 0; for(i = 0; i < wpa->queue_cnt; i++) free(wpa->queue[i].buf); free(wpa->queue); wpa->queue = NULL; wpa->queue_cnt = 0; if(wpa->channel != NULL) { g_io_channel_shutdown(wpa->channel, TRUE, NULL); g_io_channel_unref(wpa->channel); wpa->channel = NULL; wpa->fd = -1; } if(wpa->fd != -1 && close(wpa->fd) != 0) wpa->helper->error(NULL, wpa->path, 1); wpa->fd = -1; if(unlink(wpa->path) != 0) wpa->helper->error(NULL, wpa->path, 1); if(connect != TRUE) return 0; /* reconnect to the daemon */ if(_init_timeout(wpa) == FALSE) return 0; wpa->source = g_timeout_add(5000, _init_timeout, wpa); return 0; } /* callbacks */ /* on_timeout */ static gboolean _on_timeout(gpointer data) { Wpa * wpa = data; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif _wpa_queue(wpa, WC_STATUS); return TRUE; } /* on_watch_can_read */ static gboolean _read_status(Wpa * wpa, char const * buf, size_t cnt); static gboolean _read_list_networks(Wpa * wpa, char const * buf, size_t cnt); static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition, gpointer data) { int ret = FALSE; Wpa * wpa = data; WpaEntry * entry = &wpa->queue[0]; char buf[256]; /* XXX in wpa */ gsize cnt; GError * error = NULL; GIOStatus status; if(condition != G_IO_IN || source != wpa->channel || wpa->queue_cnt == 0 || entry->buf_cnt != 0) return FALSE; /* should not happen */ 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->command == WC_LIST_NETWORKS) ret = _read_list_networks(wpa, buf, cnt); else if(entry->command == WC_STATUS) ret = _read_status(wpa, buf, cnt); break; case G_IO_STATUS_ERROR: _wpa_error(wpa, error->message, 1); case G_IO_STATUS_EOF: default: /* should not happen */ wpa->source = 0; _wpa_reset(wpa, TRUE); return FALSE; } if(ret == TRUE) return TRUE; wpa->rd_source = 0; memmove(entry, &wpa->queue[1], sizeof(*entry) * (--wpa->queue_cnt)); if(wpa->queue_cnt == 0) return FALSE; /* FIXME maybe wrong */ wpa->wr_source = g_io_add_watch(wpa->channel, G_IO_OUT, _on_watch_can_write, wpa); return ret; } static gboolean _read_list_networks(Wpa * wpa, char const * buf, size_t cnt) { 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 < cnt;) { 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\t%79[^\t]\t%79[^\t]\t%79s", &u, ssid, bssid, flags)) == 4) if(strcmp(flags, "[CURRENT]") == 0) { gtk_image_set_from_stock(GTK_IMAGE(wpa->image), GTK_STOCK_CONNECT, wpa->helper->icon_size); gtk_label_set_text(GTK_LABEL(wpa->label), ssid); break; } i = j; } free(p); return FALSE; } static gboolean _read_status(Wpa * wpa, char const * buf, size_t cnt) { size_t i; size_t j; char * p = NULL; char * q; char variable[80]; char value[80]; for(i = 0; i < cnt;) { 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; if(strcmp(variable, "wpa_state") == 0) gtk_image_set_from_stock(GTK_IMAGE(wpa->image), (strcmp(value, "COMPLETED") == 0) ? GTK_STOCK_CONNECT : GTK_STOCK_DISCONNECT, wpa->helper->icon_size); if(strcmp(variable, "ssid") == 0) gtk_label_set_text(GTK_LABEL(wpa->label), value); i = j; } free(p); return FALSE; } /* on_watch_can_write */ static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition, gpointer data) { Wpa * wpa = data; WpaEntry * entry = &wpa->queue[0]; gsize cnt = 0; GError * error = NULL; GIOStatus status; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(condition != G_IO_OUT || source != wpa->channel || wpa->queue_cnt == 0 || entry->buf_cnt == 0) { wpa->wr_source = 0; return FALSE; /* should not happen */ } 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); case G_IO_STATUS_EOF: default: /* should not happen */ wpa->source = 0; _wpa_reset(wpa, TRUE); return FALSE; } if(entry->buf_cnt != 0) return TRUE; wpa->rd_source = g_io_add_watch(wpa->channel, G_IO_IN, _on_watch_can_read, wpa); wpa->wr_source = 0; return FALSE; }