diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1769767 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*~ +debian/files +debian/libgsupplicant +debian/libgsupplicant-dev +debian/*.debhelper.log +debian/*.debhelper +debian/*.substvars +debian/tmp +documentation.list +installroot +build +RPMS diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9759bf8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +Copyright (C) 2015-2017 Jolla Ltd. + +You may use this file under the terms of BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Jolla Ltd nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e5c059c --- /dev/null +++ b/Makefile @@ -0,0 +1,247 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: clean all debug release pkgconfig +.PHONY: print_debug_lib print_release_lib +.PHONY: print_debug_link print_release_link + +# +# Required packages +# + +PKGS = glib-2.0 gio-2.0 gio-unix-2.0 libglibutil + +# +# Default target +# + +all: debug release pkgconfig + +# +# Library version +# + +VERSION_MAJOR = 1 +VERSION_MINOR = 0 +VERSION_RELEASE = 0 + +# Version for pkg-config +PCVERSION = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_RELEASE) + +# +# Library name +# + +NAME = gsupplicant +LIB_NAME = lib$(NAME) +LIB_DEV_SYMLINK = $(LIB_NAME).so +LIB_SYMLINK1 = $(LIB_DEV_SYMLINK).$(VERSION_MAJOR) +LIB_SYMLINK2 = $(LIB_SYMLINK1).$(VERSION_MINOR) +LIB_SONAME = $(LIB_SYMLINK1) +LIB = $(LIB_SONAME).$(VERSION_MINOR).$(VERSION_RELEASE) + +# +# Sources +# + +SRC = \ + gsupplicant.c \ + gsupplicant_bss.c \ + gsupplicant_error.c \ + gsupplicant_interface.c \ + gsupplicant_network.c \ + gsupplicant_util.c +GEN_SRC = \ + fi.w1.wpa_supplicant1.c \ + fi.w1.wpa_supplicant1.Interface.c \ + fi.w1.wpa_supplicant1.Interface.WPS.c \ + fi.w1.wpa_supplicant1.BSS.c \ + fi.w1.wpa_supplicant1.Network.c + +# +# Directories +# + +SRC_DIR = src +INCLUDE_DIR = include +BUILD_DIR = build +GEN_DIR = $(BUILD_DIR) +SPEC_DIR = spec +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +WARNINGS = -Wall +INCLUDES = -I$(INCLUDE_DIR) -I$(GEN_DIR) +BASE_FLAGS = -fPIC +FULL_CFLAGS = $(BASE_FLAGS) $(CFLAGS) $(DEFINES) $(WARNINGS) $(INCLUDES) \ + -MMD -MP $(shell pkg-config --cflags $(PKGS)) +FULL_LDFLAGS = $(BASE_FLAGS) $(LDFLAGS) -shared -Wl,-soname -Wl,$(LIB_SONAME) \ + $(shell pkg-config --libs $(PKGS)) +DEBUG_FLAGS = -g +RELEASE_FLAGS = + +ifndef KEEP_SYMBOLS +KEEP_SYMBOLS = 0 +endif + +ifneq ($(KEEP_SYMBOLS),0) +RELEASE_FLAGS += -g +endif + +DEBUG_LDFLAGS = $(FULL_LDFLAGS) $(DEBUG_FLAGS) +RELEASE_LDFLAGS = $(FULL_LDFLAGS) $(RELEASE_FLAGS) +DEBUG_CFLAGS = $(FULL_CFLAGS) $(DEBUG_FLAGS) -DDEBUG +RELEASE_CFLAGS = $(FULL_CFLAGS) $(RELEASE_FLAGS) -O2 + +# +# Files +# + +PKGCONFIG = \ + $(BUILD_DIR)/$(LIB_NAME).pc +DEBUG_OBJS = \ + $(GEN_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(GEN_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) +GEN_FILES = $(GEN_SRC:%=$(GEN_DIR)/%) +.PRECIOUS: $(GEN_FILES) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +$(GEN_FILES): | $(GEN_DIR) +$(DEBUG_OBJS): | $(DEBUG_BUILD_DIR) +$(RELEASE_OBJS): | $(RELEASE_BUILD_DIR) + +# +# Rules +# + +DEBUG_LIB = $(DEBUG_BUILD_DIR)/$(LIB) +RELEASE_LIB = $(RELEASE_BUILD_DIR)/$(LIB) +DEBUG_LINK = $(DEBUG_BUILD_DIR)/$(LIB_SONAME) +RELEASE_LINK = $(RELEASE_BUILD_DIR)/$(LIB_SONAME) + +debug: $(DEBUG_LIB) $(DEBUG_LINK) + +release: $(RELEASE_LIB) $(RELEASE_LINK) + +pkgconfig: $(PKGCONFIG) + +print_debug_lib: + @echo $(DEBUG_LIB) + +print_release_lib: + @echo $(RELEASE_LIB) + +print_debug_link: + @echo $(DEBUG_LINK) + +print_release_link: + @echo $(RELEASE_LINK) + +clean: + rm -f *~ $(SRC_DIR)/*~ $(INCLUDE_DIR)/*~ rpm/*~ + rm -fr $(BUILD_DIR) RPMS installroot + rm -fr debian/tmp debian/lib$(NAME) debian/lib$(NAME)-dev + rm -f documentation.list debian/files debian/*.substvars + rm -f debian/*.debhelper.log debian/*.debhelper debian/*~ + make -C tools/wpa-tool clean + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(DEBUG_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c -I. $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c -I. $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_LIB): $(DEBUG_BUILD_DIR) $(DEBUG_OBJS) + $(LD) $(DEBUG_OBJS) $(DEBUG_LDFLAGS) -o $@ + +$(RELEASE_LIB): $(RELEASE_BUILD_DIR) $(RELEASE_OBJS) + $(LD) $(RELEASE_OBJS) $(RELEASE_LDFLAGS) -o $@ +ifeq ($(KEEP_SYMBOLS),0) + strip $@ +endif + +$(DEBUG_BUILD_DIR)/$(LIB_SYMLINK1): $(DEBUG_BUILD_DIR)/$(LIB_SYMLINK2) + ln -sf $(LIB_SYMLINK2) $@ + +$(RELEASE_BUILD_DIR)/$(LIB_SYMLINK1): $(RELEASE_BUILD_DIR)/$(LIB_SYMLINK2) + ln -sf $(LIB_SYMLINK2) $@ + +$(DEBUG_BUILD_DIR)/$(LIB_SYMLINK2): $(DEBUG_LIB) + ln -sf $(LIB) $@ + +$(RELEASE_BUILD_DIR)/$(LIB_SYMLINK2): $(RELEASE_LIB) + ln -sf $(LIB) $@ + +$(PKGCONFIG): $(LIB_NAME).pc.in + sed -e 's/\[version\]/'$(PCVERSION)/g $< > $@ + +# +# Install +# + +INSTALL_PERM = 644 +INSTALL_OWNER = $(shell id -u) +INSTALL_GROUP = $(shell id -g) + +INSTALL = install +INSTALL_DIRS = $(INSTALL) -d +INSTALL_FILES = $(INSTALL) -m $(INSTALL_PERM) + +INSTALL_LIB_DIR = $(DESTDIR)/usr/lib +INSTALL_INCLUDE_DIR = $(DESTDIR)/usr/include/$(NAME) +INSTALL_PKGCONFIG_DIR = $(DESTDIR)/usr/lib/pkgconfig + +install: $(INSTALL_LIB_DIR) + $(INSTALL_FILES) $(RELEASE_LIB) $(INSTALL_LIB_DIR) + ln -sf $(LIB) $(INSTALL_LIB_DIR)/$(LIB_SYMLINK2) + ln -sf $(LIB_SYMLINK2) $(INSTALL_LIB_DIR)/$(LIB_SYMLINK1) + +install-dev: install $(INSTALL_INCLUDE_DIR) $(INSTALL_PKGCONFIG_DIR) + $(INSTALL_FILES) $(INCLUDE_DIR)/*.h $(INSTALL_INCLUDE_DIR) + $(INSTALL_FILES) $(PKGCONFIG) $(INSTALL_PKGCONFIG_DIR) + ln -sf $(LIB_SYMLINK1) $(INSTALL_LIB_DIR)/$(LIB_DEV_SYMLINK) + +$(INSTALL_LIB_DIR): + $(INSTALL_DIRS) $@ + +$(INSTALL_INCLUDE_DIR): + $(INSTALL_DIRS) $@ + +$(INSTALL_PKGCONFIG_DIR): + $(INSTALL_DIRS) $@ diff --git a/README b/README new file mode 100644 index 0000000..8f86b74 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +This is a glib-based wrapper for wpa_supplicant D-Bus interfaces: + +https://w1.fi/wpa_supplicant/devel/dbus.html diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..b17478f --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +libgsupplicant (1.0.0) unstable; urgency=low + + * Initial release + + -- Slava Monich Tue, 28 Feb 2017 02:25:55 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..0759d24 --- /dev/null +++ b/debian/control @@ -0,0 +1,18 @@ +Source: libgsupplicant +Section: libs +Priority: optional +Maintainer: Slava Monich +Build-Depends: debhelper (>= 7), libglib2.0-dev (>= 2.0), libglibutil-dev +Standards-Version: 3.8.4 + +Package: libgsupplicant +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Client library for mce + +Package: libgsupplicant-dev +Section: libdevel +Architecture: any +Depends: libgsupplicant (= ${binary:Version}), ${misc:Depends} +Description: Development files for libgsupplicant diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..9759bf8 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,28 @@ +Copyright (C) 2015-2017 Jolla Ltd. + +You may use this file under the terms of BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of Jolla Ltd nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. diff --git a/debian/libgsupplicant-dev.install b/debian/libgsupplicant-dev.install new file mode 100644 index 0000000..1fd3491 --- /dev/null +++ b/debian/libgsupplicant-dev.install @@ -0,0 +1,3 @@ +debian/tmp/usr/lib/libgsupplicant.so usr/lib +include/*.h usr/include/gsupplicant +build/libgsupplicant.pc usr/lib/pkgconfig diff --git a/debian/libgsupplicant.install b/debian/libgsupplicant.install new file mode 100644 index 0000000..9efd286 --- /dev/null +++ b/debian/libgsupplicant.install @@ -0,0 +1 @@ +debian/tmp/usr/lib/libgsupplicant.so.* usr/lib diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..3a92007 --- /dev/null +++ b/debian/rules @@ -0,0 +1,11 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +override_dh_auto_install: + dh_auto_install -- install-dev + +%: + dh $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 0000000..8217775 --- /dev/null +++ b/debian/source/options @@ -0,0 +1 @@ +tar-ignore = ".git" diff --git a/include/gsupplicant.h b/include/gsupplicant.h new file mode 100644 index 0000000..886c802 --- /dev/null +++ b/include/gsupplicant.h @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_H +#define GSUPPLICANT_H + +#include + +G_BEGIN_DECLS + +typedef enum gsupplicant_property { + GSUPPLICANT_PROPERTY_ANY, + GSUPPLICANT_PROPERTY_VALID, + GSUPPLICANT_PROPERTY_CAPABILITIES, + GSUPPLICANT_PROPERTY_EAP_METHODS, + GSUPPLICANT_PROPERTY_INTERFACES, + GSUPPLICANT_PROPERTY_COUNT +} GSUPPLICANT_PROPERTY; + +typedef struct gsupplicant_priv GSupplicantPriv; + +struct gsupplicant { + GObject object; + GSupplicantPriv* priv; + gboolean valid; + gboolean failed; + const GStrV* interfaces; + GSUPPLICANT_EAP_METHOD eap_methods; + guint32 caps; + +#define GSUPPLICANT_CAPS_AP (0x00000001) +#define GSUPPLICANT_CAPS_IBSS_RSN (0x00000002) +#define GSUPPLICANT_CAPS_P2P (0x00000004) +#define GSUPPLICANT_CAPS_INTERWORKING (0x00000008) +}; + +typedef struct gsupplicant_create_interface_params { + const char* ifname; + const char* bridge_ifname; + const char* driver; + const char* config_file; +} GSupplicantCreateInterfaceParams; + +typedef +void +(*GSupplicantFunc)( + GSupplicant* supplicant, + void* data); + +typedef +void +(*GSupplicantPropertyFunc)( + GSupplicant* supplicant, + GSUPPLICANT_PROPERTY property, + void* data); + +typedef +void +(*GSupplicantResultFunc)( + GSupplicant* supplicant, + GCancellable* cancel, + const GError* error, + void* data); + +typedef +void +(*GSupplicantStringResultFunc)( + GSupplicant* supplicant, + GCancellable* cancel, + const GError* error, + const char* result, + void* data); + +GSupplicant* +gsupplicant_new( + void); + +GSupplicant* +gsupplicant_ref( + GSupplicant* supplicant); + +void +gsupplicant_unref( + GSupplicant* supplicant); + +gulong +gsupplicant_add_handler( + GSupplicant* self, + GSUPPLICANT_PROPERTY prop, + GSupplicantFunc fn, + void* data); + +gulong +gsupplicant_add_property_changed_handler( + GSupplicant* self, + GSUPPLICANT_PROPERTY property, + GSupplicantPropertyFunc fn, + void* data); + +void +gsupplicant_remove_handler( + GSupplicant* supplicant, + gulong id); + +void +gsupplicant_remove_handlers( + GSupplicant* supplicant, + gulong* ids, + guint count); + +GCancellable* +gsupplicant_create_interface( + GSupplicant* supplicant, + const GSupplicantCreateInterfaceParams* params, + GSupplicantStringResultFunc fn, + void* data); + +GCancellable* +gsupplicant_remove_interface( + GSupplicant* supplicant, + const char* path, + GSupplicantResultFunc fn, + void* data); + +GCancellable* +gsupplicant_get_interface( + GSupplicant* supplicant, + const char* ifname, + GSupplicantStringResultFunc fn, + void* data); + +const char* +gsupplicant_caps_name( + guint caps, + guint* cap); + +const char* +gsupplicant_eap_method_name( + guint methods, + guint* method); + +const char* +gsupplicant_cipher_suite_name( + guint cipher_suites, + guint* cipher_suite); + +const char* +gsupplicant_keymgmt_suite_name( + guint keymgmt_suites, + guint* keymgmt_suite); + +G_END_DECLS + +#endif /* GSUPPLICANT_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/include/gsupplicant_bss.h b/include/gsupplicant_bss.h new file mode 100644 index 0000000..24a5ab4 --- /dev/null +++ b/include/gsupplicant_bss.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_BSS_H +#define GSUPPLICANT_BSS_H + +#include + +G_BEGIN_DECLS + +typedef struct gsupplicant_bss_wpa { + GSUPPLICANT_KEYMGMT keymgmt; + GSUPPLICANT_CIPHER pairwise; + GSUPPLICANT_CIPHER group; +} GSupplicantBSSWPA; + +typedef struct gsupplicant_bss_rsn { + GSUPPLICANT_KEYMGMT keymgmt; + GSUPPLICANT_CIPHER pairwise; + GSUPPLICANT_CIPHER group; + GSUPPLICANT_CIPHER mgmt_group; +} GSupplicantBSSRSN; + +typedef struct gsupplicant_bss_connect_params { + guint flags; /* Should be zero */ + GSUPPLICANT_AUTH_FLAGS auth_flags; + GSUPPLICANT_EAP_METHOD eap; + const char* bgscan; + const char* passphrase; + /* EAP */ + const char* identity; + const char* anonymous_identity; + const char* ca_cert_file; + const char* client_cert_file; + const char* private_key_file; + const char* private_key_passphrase; + const char* subject_match; + const char* altsubject_match; + const char* domain_suffix_match; + const char* domain_match; + GSUPPLICANT_EAP_METHOD phase2; + const char* ca_cert_file2; + const char* client_cert_file2; + const char* private_key_file2; + const char* private_key_passphrase2; + const char* subject_match2; + const char* altsubject_match2; + const char* domain_suffix_match2; +} GSupplicantBSSConnectParams; + +typedef enum gsupplicant_bss_property { + GSUPPLICANT_BSS_PROPERTY_ANY, + GSUPPLICANT_BSS_PROPERTY_VALID, + GSUPPLICANT_BSS_PROPERTY_PRESENT, + GSUPPLICANT_BSS_PROPERTY_SSID, + GSUPPLICANT_BSS_PROPERTY_BSSID, + GSUPPLICANT_BSS_PROPERTY_WPA, + GSUPPLICANT_BSS_PROPERTY_RSN, + GSUPPLICANT_BSS_PROPERTY_MODE, + GSUPPLICANT_BSS_PROPERTY_WPS_CAPS, + GSUPPLICANT_BSS_PROPERTY_IES, + GSUPPLICANT_BSS_PROPERTY_PRIVACY, + GSUPPLICANT_BSS_PROPERTY_FREQUENCY, + GSUPPLICANT_BSS_PROPERTY_RATES, + GSUPPLICANT_BSS_PROPERTY_MAXRATE, + GSUPPLICANT_BSS_PROPERTY_SIGNAL, + GSUPPLICANT_BSS_PROPERTY_COUNT +} GSUPPLICANT_BSS_PROPERTY; + +typedef enum gsupplicant_bss_mode { + GSUPPLICANT_BSS_MODE_UNKNOWN, + GSUPPLICANT_BSS_MODE_INFRA, + GSUPPLICANT_BSS_MODE_AD_HOC +} GSUPPLICANT_BSS_MODE; + +typedef struct gsupplicant_bss_priv GSupplicantBSSPriv; + +struct gsupplicant_bss { + GObject object; + GSupplicantBSSPriv* priv; + GSupplicantInterface* iface; + const char* path; + gboolean valid; + gboolean present; + GBytes* bssid; + GBytes* ssid; + const char* ssid_str; + const GSupplicantBSSWPA* wpa; + const GSupplicantBSSRSN* rsn; + GSUPPLICANT_WPS_CAPS wps_caps; + GSUPPLICANT_BSS_MODE mode; + GBytes* ies; + gboolean privacy; + guint frequency; + const GSupplicantUIntArray* rates; + guint maxrate; + gint signal; +}; + +typedef +void +(*GSupplicantBSSFunc)( + GSupplicantBSS* bss, + void* data); + +typedef +void +(*GSupplicantBSSStringResultFunc)( + GSupplicantBSS* bss, + GCancellable* cancel, + const GError* error, + const char* result, + void* data); + +typedef +void +(*GSupplicantBSSPropertyFunc)( + GSupplicantBSS* bss, + GSUPPLICANT_BSS_PROPERTY property, + void* data); + +GSupplicantBSS* +gsupplicant_bss_new( + const char* path); + +GSupplicantBSS* +gsupplicant_bss_ref( + GSupplicantBSS* bss); + +void +gsupplicant_bss_unref( + GSupplicantBSS* bss); + +gulong +gsupplicant_bss_add_handler( + GSupplicantBSS* bss, + GSUPPLICANT_BSS_PROPERTY property, + GSupplicantBSSFunc fn, + void* data); + +gulong +gsupplicant_bss_add_property_changed_handler( + GSupplicantBSS* bss, + GSUPPLICANT_BSS_PROPERTY property, + GSupplicantBSSPropertyFunc fn, + void* data); + +void +gsupplicant_bss_remove_handler( + GSupplicantBSS* bss, + gulong id); + +void +gsupplicant_bss_remove_handlers( + GSupplicantBSS* bss, + gulong* ids, + guint count); + +GSUPPLICANT_SECURITY +gsupplicant_bss_security( + GSupplicantBSS* bss); + +GSUPPLICANT_KEYMGMT +gsupplicant_bss_keymgmt( + GSupplicantBSS* bss); + +GSUPPLICANT_CIPHER +gsupplicant_bss_pairwise( + GSupplicantBSS* bss); + +GCancellable* +gsupplicant_bss_connect( + GSupplicantBSS* bss, + const GSupplicantBSSConnectParams* params, + guint flags, /* None defined yet */ + GSupplicantBSSStringResultFunc fn, + void* data); + +G_END_DECLS + +#endif /* GSUPPLICANT_BSS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/include/gsupplicant_error.h b/include/gsupplicant_error.h new file mode 100644 index 0000000..dcca36a --- /dev/null +++ b/include/gsupplicant_error.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_ERROR_H +#define GSUPPLICANT_ERROR_H + +#include + +G_BEGIN_DECLS + +#define GSUPPLICANT_ERROR (gsupplicant_error_quark()) +GQuark gsupplicant_error_quark(void); + +#define GSUPPLICANT_ERRORS(e) \ + e(UNKNOWN_ERROR, "UnknownError") \ + e(INVALID_ARGS, "InvalidArgs") \ + e(NO_MEMORY, "NoMemory") \ + e(NOT_CONNECTED, "NotConnected") \ + e(NETWORK_UNKNOWN, "NetworkUnknown") \ + e(INTERFACE_UNKNOWN, "InterfaceUnknown") \ + e(INTERFACE_DISABLED, "InterfaceDisabled") \ + e(BLOB_UNKNOWN, "BlobUnknown") \ + e(BLOB_EXISTS, "BlobExists") \ + e(NO_SUBSCRIPTION, "NoSubscription") \ + e(SUBSCRIPTION_IN_USE, "SubscriptionInUse") \ + e(SUBSCRIPTION_NOT_YOU, "SubscriptionNotYou") + +typedef enum gsupplicant_error_code { +#define GSUPPLICANT_ERROR_ENUM_(E,e) GSUPPLICANT_ERROR_##E, + GSUPPLICANT_ERRORS(GSUPPLICANT_ERROR_ENUM_) +#undef GSUPPLICANT_ERROR_ENUM_ +} GSUPPLICANT_ERROR_CODE; + +gboolean +gsupplicant_is_error( + const GError* error, + GSUPPLICANT_ERROR_CODE code); + +G_END_DECLS + +#endif /* GSUPPLICANT_ERROR_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/include/gsupplicant_interface.h b/include/gsupplicant_interface.h new file mode 100644 index 0000000..44c0d5e --- /dev/null +++ b/include/gsupplicant_interface.h @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_INTERFACE_H +#define GSUPPLICANT_INTERFACE_H + +#include + +G_BEGIN_DECLS + +typedef enum gsupplicant_interface_state { + GSUPPLICANT_INTERFACE_STATE_UNKNOWN, + GSUPPLICANT_INTERFACE_STATE_DISCONNECTED, + GSUPPLICANT_INTERFACE_STATE_INACTIVE, + GSUPPLICANT_INTERFACE_STATE_SCANNING, + GSUPPLICANT_INTERFACE_STATE_AUTHENTICATING, + GSUPPLICANT_INTERFACE_STATE_ASSOCIATING, + GSUPPLICANT_INTERFACE_STATE_ASSOCIATED, + GSUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE, + GSUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, + GSUPPLICANT_INTERFACE_STATE_COMPLETED +} GSUPPLICANT_INTERFACE_STATE; + +typedef enum gsupplicant_interface_property { + GSUPPLICANT_INTERFACE_PROPERTY_ANY, + GSUPPLICANT_INTERFACE_PROPERTY_VALID, + GSUPPLICANT_INTERFACE_PROPERTY_PRESENT, + GSUPPLICANT_INTERFACE_PROPERTY_CAPS, + GSUPPLICANT_INTERFACE_PROPERTY_STATE, + GSUPPLICANT_INTERFACE_PROPERTY_WPS_CREDENTIALS, + GSUPPLICANT_INTERFACE_PROPERTY_SCANNING, + GSUPPLICANT_INTERFACE_PROPERTY_AP_SCAN, + GSUPPLICANT_INTERFACE_PROPERTY_COUNTRY, + GSUPPLICANT_INTERFACE_PROPERTY_DRIVER, + GSUPPLICANT_INTERFACE_PROPERTY_IFNAME, + GSUPPLICANT_INTERFACE_PROPERTY_BRIDGE_IFNAME, + GSUPPLICANT_INTERFACE_PROPERTY_CURRENT_BSS, + GSUPPLICANT_INTERFACE_PROPERTY_CURRENT_NETWORK, + GSUPPLICANT_INTERFACE_PROPERTY_BSSS, + GSUPPLICANT_INTERFACE_PROPERTY_NETWORKS, + GSUPPLICANT_INTERFACE_PROPERTY_SCAN_INTERVAL, + GSUPPLICANT_INTERFACE_PROPERTY_COUNT +} GSUPPLICANT_INTERFACE_PROPERTY; + +typedef struct gsupplicant_interface_caps { + GSUPPLICANT_KEYMGMT keymgmt; + GSUPPLICANT_CIPHER pairwise; + GSUPPLICANT_CIPHER group; + GSUPPLICANT_PROTOCOL protocol; + GSUPPLICANT_AUTH auth_alg; + guint scan; + +#define GSUPPLICANT_INTERFACE_CAPS_SCAN_ACTIVE (0x00000001) +#define GSUPPLICANT_INTERFACE_CAPS_SCAN_PASSIVE (0x00000002) +#define GSUPPLICANT_INTERFACE_CAPS_SCAN_SSID (0x00000004) + + guint modes; + +#define GSUPPLICANT_INTERFACE_CAPS_MODES_INFRA (0x00000001) +#define GSUPPLICANT_INTERFACE_CAPS_MODES_AD_HOC (0x00000002) +#define GSUPPLICANT_INTERFACE_CAPS_MODES_AP (0x00000004) +#define GSUPPLICANT_INTERFACE_CAPS_MODES_P2P (0x00000008) + + gint max_scan_ssid; + guint caps_reserved[2]; +} GSupplicantInterfaceCaps; + +typedef struct gsupplicant_signal_poll { + guint flags; /* Fields validity flags: */ + +#define GSUPPLICANT_SIGNAL_POLL_LINKSPEED (0x01) +#define GSUPPLICANT_SIGNAL_POLL_NOISE (0x02) +#define GSUPPLICANT_SIGNAL_POLL_FREQUENCY (0x04) +#define GSUPPLICANT_SIGNAL_POLL_RSSI (0x08) +#define GSUPPLICANT_SIGNAL_POLL_AVG_RSSI (0x10) +#define GSUPPLICANT_SIGNAL_POLL_CENTER_FRQ1 (0x20) +#define GSUPPLICANT_SIGNAL_POLL_CENTER_FRQ2 (0x40) + + gint linkspeed; /* Link speed (Mbps) */ + gint noise; /* Noise (dBm) */ + guint frequency; /* Frequency (MHz) */ + gint rssi; /* RSSI (dBm) */ + gint avg_rssi; /* Average RSSI (dBm) */ + gint center_frq1; /* VHT segment 1 frequency (MHz) */ + gint center_frq2; /* VHT segment 2 frequency (MHz) */ +} GSupplicantSignalPoll; + +typedef enum gsupplicant_scan_type { + GSUPPLICANT_SCAN_TYPE_PASSIVE, + GSUPPLICANT_SCAN_TYPE_ACTIVE +} GSUPPLICANT_SCAN_TYPE; + +typedef struct gsupplicant_scan_frequency { + guint center; + guint width; +} GSupplicantScanFrequency; + +typedef struct gsupplicant_scan_frequencies { + const GSupplicantScanFrequency* freq; + guint count; +} GSupplicantScanFrequencies; + +typedef struct gsupplicant_scan_params { + guint flags; + +#define GSUPPLICANT_SCAN_PARAM_ALLOW_ROAM (0x01) + + GSUPPLICANT_SCAN_TYPE type; + GBytes** ssids; + GBytes** ies; + const GSupplicantScanFrequencies* channels; + gboolean allow_roam; +} GSupplicantScanParams; + +typedef struct gsupplicant_network_params { + guint flags; /* Should be zero */ + GSUPPLICANT_AUTH_FLAGS auth_flags; + GBytes* ssid; + GSUPPLICANT_OP_MODE mode; + GSUPPLICANT_EAP_METHOD eap; + guint scan_ssid; + guint frequency; + GSUPPLICANT_SECURITY security; + GSUPPLICANT_PROTOCOL protocol; + GSUPPLICANT_CIPHER pairwise; + GSUPPLICANT_CIPHER group; + const char* bgscan; + const char* passphrase; + /* EAP */ + const char* identity; + const char* anonymous_identity; + const char* ca_cert_file; + const char* client_cert_file; + const char* private_key_file; + const char* private_key_passphrase; + const char* subject_match; + const char* altsubject_match; + const char* domain_suffix_match; + const char* domain_match; + GSUPPLICANT_EAP_METHOD phase2; + const char* ca_cert_file2; + const char* client_cert_file2; + const char* private_key_file2; + const char* private_key_passphrase2; + const char* subject_match2; + const char* altsubject_match2; + const char* domain_suffix_match2; +} GSupplicantNetworkParams; + +typedef struct gsupplicant_wps_params { + GSUPPLICANT_WPS_ROLE role; + GSUPPLICANT_WPS_AUTH auth; + const char* pin; + GBytes* bssid; + GBytes* p2p_address; +} GSupplicantWPSParams; + +typedef struct gsupplicant_wps_credentials { + GBytes* bssid; + GBytes* ssid; + GSUPPLICANT_AUTH auth_types; + GSUPPLICANT_WPS_ENCR encr_types; + GBytes* key; + guint key_index; +} GSupplicantWPSCredentials; + +typedef struct gsupplicant_interface_priv GSupplicantInterfacePriv; + +struct gsupplicant_interface { + GObject object; + GSupplicantInterfacePriv* priv; + GSupplicant* supplicant; + const char* path; + gboolean valid; + gboolean present; + GSupplicantInterfaceCaps caps; + GSUPPLICANT_INTERFACE_STATE state; + const GSupplicantWPSCredentials* wps_credentials; + gboolean scanning; + guint ap_scan; + gint scan_interval; + const char* country; + const char* driver; + const char* ifname; + const char* bridge_ifname; + const char* current_bss; + const char* current_network; + const GStrV* bsss; + const GStrV* networks; +}; + +typedef +void +(*GSupplicantInterfaceFunc)( + GSupplicantInterface* iface, + void* data); + +typedef +void +(*GSupplicantInterfacePropertyFunc)( + GSupplicantInterface* iface, + GSUPPLICANT_INTERFACE_PROPERTY property, + void* data); + +typedef +void +(*GSupplicantInterfaceResultFunc)( + GSupplicantInterface* iface, + GCancellable* cancel, + const GError* error, + void* data); + +typedef +void +(*GSupplicantInterfaceStringResultFunc)( + GSupplicantInterface* iface, + GCancellable* cancel, + const GError* error, + const char* result, + void* data); + +typedef +void +(*GSupplicantInterfaceSignalPollResultFunc)( + GSupplicantInterface* iface, + GCancellable* cancel, + const GError* error, + const GSupplicantSignalPoll* result, + void* data); + +GSupplicantInterface* +gsupplicant_interface_new( + const char* path); + +GSupplicantInterface* +gsupplicant_interface_ref( + GSupplicantInterface* iface); + +void +gsupplicant_interface_unref( + GSupplicantInterface* iface); + +gulong +gsupplicant_interface_add_handler( + GSupplicantInterface* iface, + GSUPPLICANT_INTERFACE_PROPERTY property, + GSupplicantInterfaceFunc fn, + void* data); + +gulong +gsupplicant_interface_add_property_changed_handler( + GSupplicantInterface* iface, + GSUPPLICANT_INTERFACE_PROPERTY property, + GSupplicantInterfacePropertyFunc fn, + void* data); + +gboolean +gsupplicant_interface_set_ap_scan( + GSupplicantInterface* iface, + guint ap_scan); + +gboolean +gsupplicant_interface_set_country( + GSupplicantInterface* iface, + const char* country); + +void +gsupplicant_interface_remove_handler( + GSupplicantInterface* iface, + gulong id); + +void +gsupplicant_interface_remove_handlers( + GSupplicantInterface* iface, + gulong* ids, + guint count); + +GCancellable* +gsupplicant_interface_disconnect( + GSupplicantInterface* iface, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_reassociate( + GSupplicantInterface* iface, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_reconnect( + GSupplicantInterface* iface, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_reattach( + GSupplicantInterface* iface, + GSupplicantInterfaceResultFunc fn, + void* data); + +#define GSUPPLICANT_ADD_NETWORK_DELETE_OTHER (0x01) +#define GSUPPLICANT_ADD_NETWORK_SELECT (0x02) +#define GSUPPLICANT_ADD_NETWORK_ENABLE (0x04) + +GCancellable* +gsupplicant_interface_add_network( + GSupplicantInterface* iface, + const GSupplicantNetworkParams* params, + guint flags, /* See above */ + GSupplicantInterfaceStringResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_add_network_full( + GSupplicantInterface* iface, + GCancellable* cancel, + const GSupplicantNetworkParams* params, + guint flags, /* See above */ + GSupplicantInterfaceStringResultFunc fn, + GDestroyNotify destroy, + void* data); + +GCancellable* +gsupplicant_interface_select_network( + GSupplicantInterface* iface, + const char* path, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_remove_network( + GSupplicantInterface* iface, + const char* path, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_remove_all_networks( + GSupplicantInterface* iface, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_remove_all_networks_full( + GSupplicantInterface* iface, + GCancellable* cancel, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify destroy, + void* data); + +GCancellable* +gsupplicant_interface_wps_connect( + GSupplicantInterface* iface, + const GSupplicantWPSParams* params, + GSupplicantInterfaceStringResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_wps_connect_full( + GSupplicantInterface* iface, + GCancellable* cancel, + const GSupplicantWPSParams* params, + gint timeout_sec, /* 0 = default, negative = no timeout */ + GSupplicantInterfaceStringResultFunc fn, + GDestroyNotify destroy, + void* data); + +GCancellable* +gsupplicant_interface_wps_cancel( + GSupplicantInterface* iface, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_scan( + GSupplicantInterface* iface, + const GSupplicantScanParams* params, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_auto_scan( + GSupplicantInterface* iface, + const char* param, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_remove_flush_bss( + GSupplicantInterface* iface, + guint age, + GSupplicantInterfaceResultFunc fn, + void* data); + +GCancellable* +gsupplicant_interface_signal_poll( + GSupplicantInterface* iface, + GSupplicantInterfaceSignalPollResultFunc fn, + void* data); + +const char* +gsupplicant_interface_state_name( + GSUPPLICANT_INTERFACE_STATE state); + +G_INLINE_FUNC const char* +gsupplicant_interface_get_state_name(GSupplicantInterface* iface) + { return iface ? gsupplicant_interface_state_name(iface->state) : NULL; } + +G_END_DECLS + +#endif /* GSUPPLICANT_INTERFACE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/include/gsupplicant_network.h b/include/gsupplicant_network.h new file mode 100644 index 0000000..73f009f --- /dev/null +++ b/include/gsupplicant_network.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_NETWORK_H +#define GSUPPLICANT_NETWORK_H + +#include + +G_BEGIN_DECLS + +typedef enum gsupplicant_network_property { + GSUPPLICANT_NETWORK_PROPERTY_ANY, + GSUPPLICANT_NETWORK_PROPERTY_VALID, + GSUPPLICANT_NETWORK_PROPERTY_PRESENT, + GSUPPLICANT_NETWORK_PROPERTY_ENABLED, + GSUPPLICANT_NETWORK_PROPERTY_PROPERTIES, + GSUPPLICANT_NETWORK_PROPERTY_COUNT +} GSUPPLICANT_NETWORK_PROPERTY; + +typedef struct gsupplicant_network_priv GSupplicantNetworkPriv; + +struct gsupplicant_network { + GObject object; + GSupplicantNetworkPriv* priv; + GSupplicantInterface* iface; + const char* path; + gboolean valid; + gboolean present; + GHashTable* properties; + gboolean enabled; +}; + +typedef +void +(*GSupplicantNetworkFunc)( + GSupplicantNetwork* network, + void* data); + +typedef +void +(*GSupplicantNetworkPropertyFunc)( + GSupplicantNetwork* network, + GSUPPLICANT_NETWORK_PROPERTY property, + void* data); + +GSupplicantNetwork* +gsupplicant_network_new( + const char* path); + +GSupplicantNetwork* +gsupplicant_network_ref( + GSupplicantNetwork* network); + +void +gsupplicant_network_unref( + GSupplicantNetwork* network); + +gulong +gsupplicant_network_add_handler( + GSupplicantNetwork* network, + GSUPPLICANT_NETWORK_PROPERTY property, + GSupplicantNetworkFunc fn, + void* data); + +gulong +gsupplicant_network_add_property_changed_handler( + GSupplicantNetwork* network, + GSUPPLICANT_NETWORK_PROPERTY property, + GSupplicantNetworkPropertyFunc fn, + void* data); + +void +gsupplicant_network_remove_handler( + GSupplicantNetwork* network, + gulong id); + +void +gsupplicant_network_remove_handlers( + GSupplicantNetwork* network, + gulong* ids, + guint count); + +gboolean +gsupplicant_network_set_enabled( + GSupplicantNetwork* self, + gboolean enabled); + +G_END_DECLS + +#endif /* GSUPPLICANT_NETWORK_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/include/gsupplicant_types.h b/include/gsupplicant_types.h new file mode 100644 index 0000000..8e25732 --- /dev/null +++ b/include/gsupplicant_types.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_TYPES_H +#define GSUPPLICANT_TYPES_H + +#include +#include + +G_BEGIN_DECLS + +#define GSUPPLICANT_INLINE static inline +#define GSUPPLICANT_LOG_MODULE gsupplicant_log + +typedef struct gsupplicant GSupplicant; +typedef struct gsupplicant_bss GSupplicantBSS; +typedef struct gsupplicant_network GSupplicantNetwork; +typedef struct gsupplicant_interface GSupplicantInterface; + +typedef enum gsupplicant_cipher { + GSUPPLICANT_CIPHER_INVALID = (0x00000000), + GSUPPLICANT_CIPHER_NONE = (0x00000001), + GSUPPLICANT_CIPHER_CCMP = (0x00000002), + GSUPPLICANT_CIPHER_TKIP = (0x00000004), + GSUPPLICANT_CIPHER_WEP104 = (0x00000008), + GSUPPLICANT_CIPHER_WEP40 = (0x00000010), + GSUPPLICANT_CIPHER_AES128_CMAC = (0x00000020) +} GSUPPLICANT_CIPHER; + +typedef enum gsupplicant_keymgmt { + GSUPPLICANT_KEYMGMT_INVALID = (0x00000000), + GSUPPLICANT_KEYMGMT_NONE = (0x00000001), + GSUPPLICANT_KEYMGMT_WPA_PSK = (0x00000002), + GSUPPLICANT_KEYMGMT_WPA_FT_PSK = (0x00000004), + GSUPPLICANT_KEYMGMT_WPA_PSK_SHA256 = (0x00000008), + GSUPPLICANT_KEYMGMT_WPA_EAP = (0x00000010), + GSUPPLICANT_KEYMGMT_WPA_FT_EAP = (0x00000020), + GSUPPLICANT_KEYMGMT_WPA_EAP_SHA256 = (0x00000040), + GSUPPLICANT_KEYMGMT_IEEE8021X = (0x00000080), + GSUPPLICANT_KEYMGMT_WPA_NONE = (0x00000100), + GSUPPLICANT_KEYMGMT_WPS = (0x00000200) +} GSUPPLICANT_KEYMGMT; + +typedef enum gsupplicant_protocol { + GSUPPLICANT_PROTOCOL_NONE = (0x00000000), + GSUPPLICANT_PROTOCOL_RSN = (0x00000001), + GSUPPLICANT_PROTOCOL_WPA = (0x00000002) +} GSUPPLICANT_PROTOCOL; + +typedef enum gsupplicant_auth { + GSUPPLICANT_AUTH_NONE = (0x00000000), + GSUPPLICANT_AUTH_OPEN = (0x00000001), + GSUPPLICANT_AUTH_SHARED = (0x00000002), + GSUPPLICANT_AUTH_LEAP = (0x00000004), + GSUPPLICANT_AUTH_WPA_PSK = (0x00000010), + GSUPPLICANT_AUTH_WPA_EAP = (0x00000020), + GSUPPLICANT_AUTH_WPA2_EAP = (0x00000040), + GSUPPLICANT_AUTH_WPA2_PSK = (0x00000080) +} GSUPPLICANT_AUTH; + +typedef enum gsupplicant_wps_caps { + GSUPPLICANT_WPS_NONE = (0x00000000), + GSUPPLICANT_WPS_SUPPORTED = (0x00000001), + GSUPPLICANT_WPS_CONFIGURED = (0x00000002), + GSUPPLICANT_WPS_PUSH_BUTTON = (0x00000004), + GSUPPLICANT_WPS_PIN = (0x00000008), + GSUPPLICANT_WPS_REGISTRAR = (0x00000010) +} GSUPPLICANT_WPS_CAPS; + +typedef enum gsupplicant_wps_role { + GSUPPLICANT_WPS_ROLE_NONE = (0x00000000), + GSUPPLICANT_WPS_ROLE_ENROLLEE = (0x00000001), + GSUPPLICANT_WPS_ROLE_REGISTRAR = (0x00000002) +} GSUPPLICANT_WPS_ROLE; + +typedef enum gsupplicant_wps_auth { + GSUPPLICANT_WPS_AUTH_NONE = (0x00000000), + GSUPPLICANT_WPS_AUTH_PUSH_BUTTON = (0x00000001), + GSUPPLICANT_WPS_AUTH_PIN = (0x00000002) +} GSUPPLICANT_WPS_AUTH; + +typedef enum gsupplicant_wps_encr { + GSUPPLICANT_WPS_ENCR_NONE = (0x00000001), + GSUPPLICANT_WPS_ENCR_WEP = (0x00000002), + GSUPPLICANT_WPS_ENCR_TKIP = (0x00000004), + GSUPPLICANT_WPS_ENCR_AES = (0x00000008) +} GSUPPLICANT_WPS_ENCR; + +typedef enum gsupplicant_eap_method { + GSUPPLICANT_EAP_METHOD_NONE = (0x00000000), + GSUPPLICANT_EAP_METHOD_PEAP = (0x00000001), + GSUPPLICANT_EAP_METHOD_TTLS = (0x00000002), + GSUPPLICANT_EAP_METHOD_TLS = (0x00000004), + GSUPPLICANT_EAP_METHOD_MSCHAPV2 = (0x00000008), + GSUPPLICANT_EAP_METHOD_MD5 = (0x00000010), + GSUPPLICANT_EAP_METHOD_GTC = (0x00000020), + GSUPPLICANT_EAP_METHOD_OTP = (0x00000040), + GSUPPLICANT_EAP_METHOD_SIM = (0x00000080), + GSUPPLICANT_EAP_METHOD_LEAP = (0x00000100), + GSUPPLICANT_EAP_METHOD_PSK = (0x00000200), + GSUPPLICANT_EAP_METHOD_AKA = (0x00000400), + GSUPPLICANT_EAP_METHOD_FAST = (0x00000800), + GSUPPLICANT_EAP_METHOD_PAX = (0x00001000), + GSUPPLICANT_EAP_METHOD_SAKE = (0x00002000), + GSUPPLICANT_EAP_METHOD_GPSK = (0x00004000), + GSUPPLICANT_EAP_METHOD_WSC = (0x00008000), + GSUPPLICANT_EAP_METHOD_IKEV2 = (0x00010000), + GSUPPLICANT_EAP_METHOD_TNC = (0x00020000), + GSUPPLICANT_EAP_METHOD_PWD = (0x00040000) +} GSUPPLICANT_EAP_METHOD; + +typedef enum gsupplicant_auth_fags { + GSUPPLICANT_AUTH_DEFAULT = (0x00000000), + GSUPPLICANT_AUTH_PHASE2_AUTHEAP = (0x00000001) +} GSUPPLICANT_AUTH_FLAGS; + +typedef enum gsupplicant_op_mode { + GSUPPLICANT_OP_MODE_INFRA, + GSUPPLICANT_OP_MODE_IBSS, + GSUPPLICANT_OP_MODE_AP +} GSUPPLICANT_OP_MODE; + +typedef enum gsupplicant_security { + GSUPPLICANT_SECURITY_NONE, + GSUPPLICANT_SECURITY_WEP, + GSUPPLICANT_SECURITY_PSK, + GSUPPLICANT_SECURITY_EAP, +} GSUPPLICANT_SECURITY; + +typedef struct gsupplicant_uint_array { + const guint* values; + guint count; +} GSupplicantUIntArray; + +extern GLogModule GSUPPLICANT_LOG_MODULE; + +G_END_DECLS + +#endif /* GSUPPLICANT_TYPES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/libgsupplicant.pc.in b/libgsupplicant.pc.in new file mode 100644 index 0000000..4b965f0 --- /dev/null +++ b/libgsupplicant.pc.in @@ -0,0 +1,10 @@ +name=gsupplicant +libdir=/usr/lib +includedir=/usr/include + +Name: libgsupplicant +Description: Client library for wpa_supplicant +Version: [version] +Requires: glib-2.0 gio-2.0 +Libs: -L${libdir} -l${name} +Cflags: -I${includedir} -I${includedir}/${name} diff --git a/rpm/libgsupplicant.spec b/rpm/libgsupplicant.spec new file mode 100644 index 0000000..4679de5 --- /dev/null +++ b/rpm/libgsupplicant.spec @@ -0,0 +1,49 @@ +Name: libgsupplicant +Version: 1.0.0 +Release: 0 +Summary: Client library for wpa_supplicant +Group: Development/Libraries +License: BSD +URL: https://git.merproject.org/mer-core/libgsupplicant +Source: %{name}-%{version}.tar.bz2 +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(gio-2.0) +BuildRequires: pkgconfig(libglibutil) >= 1.0.13 +Requires: libglibutil >= 1.0.13 +Requires(post): /sbin/ldconfig +Requires(postun): /sbin/ldconfig + +%description +Provides glib-based wpa_supplicant client API + +%package devel +Summary: Development library for %{name} +Requires: %{name} = %{version} +Requires: pkgconfig + +%description devel +This package contains the development library for %{name}. + +%prep +%setup -q + +%build +make KEEP_SYMBOLS=1 release pkgconfig + +%install +rm -rf %{buildroot} +make install-dev DESTDIR=%{buildroot} + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%defattr(-,root,root,-) +%{_libdir}/%{name}.so.* + +%files devel +%defattr(-,root,root,-) +%{_libdir}/pkgconfig/*.pc +%{_libdir}/%{name}.so +%{_includedir}/gsupplicant/*.h diff --git a/spec/fi.w1.wpa_supplicant1.BSS.xml b/spec/fi.w1.wpa_supplicant1.BSS.xml new file mode 100644 index 0000000..b0eb7c1 --- /dev/null +++ b/spec/fi.w1.wpa_supplicant1.BSS.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/fi.w1.wpa_supplicant1.Interface.WPS.xml b/spec/fi.w1.wpa_supplicant1.Interface.WPS.xml new file mode 100644 index 0000000..02676ea --- /dev/null +++ b/spec/fi.w1.wpa_supplicant1.Interface.WPS.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/fi.w1.wpa_supplicant1.Interface.xml b/spec/fi.w1.wpa_supplicant1.Interface.xml new file mode 100644 index 0000000..bab8e08 --- /dev/null +++ b/spec/fi.w1.wpa_supplicant1.Interface.xml @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/fi.w1.wpa_supplicant1.Network.xml b/spec/fi.w1.wpa_supplicant1.Network.xml new file mode 100644 index 0000000..00b67ab --- /dev/null +++ b/spec/fi.w1.wpa_supplicant1.Network.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/spec/fi.w1.wpa_supplicant1.xml b/spec/fi.w1.wpa_supplicant1.xml new file mode 100644 index 0000000..3678228 --- /dev/null +++ b/spec/fi.w1.wpa_supplicant1.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/gsupplicant.c b/src/gsupplicant.c new file mode 100644 index 0000000..456ba02 --- /dev/null +++ b/src/gsupplicant.c @@ -0,0 +1,1013 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant.h" +#include "gsupplicant_dbus.h" +#include "gsupplicant_util_p.h" +#include "gsupplicant_error.h" +#include "gsupplicant_log.h" + +#include +#include + +/* Generated headers */ +#include "fi.w1.wpa_supplicant1.h" + +/* Log module */ +GLOG_MODULE_DEFINE("gsupplicant"); + +/* Object definition */ +enum gsupplicant_proxy_handler_id { + PROXY_GPROPERTIES_CHANGED, + PROXY_PROPERTIES_CHANGED, + PROXY_INTERFACE_ADDED, + PROXY_INTERFACE_REMOVED, + PROXY_HANDLER_COUNT +}; + +struct gsupplicant_priv { + GDBusConnection* bus; + FiW1Wpa_supplicant1* proxy; + guint32 pending_signals; + char* name_owner; + GStrV* interfaces; + gulong proxy_handler_id[PROXY_HANDLER_COUNT]; +}; + +typedef GObjectClass GSupplicantClass; +G_DEFINE_TYPE(GSupplicant, gsupplicant, G_TYPE_OBJECT) +#define GSUPPLICANT_TYPE (gsupplicant_get_type()) +#define GSUPPLICANT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GSUPPLICANT_TYPE, GSupplicant)) + +typedef union gsupplicant_call_func_union { + GCallback cb; + GSupplicantResultFunc fn_void; + GSupplicantStringResultFunc fn_string; +} GSupplicantCallFuncUnion; + +typedef +void +(*GSupplicantCallFinishFunc)( + GSupplicant* supplicant, + GCancellable* cancel, + GAsyncResult* result, + GSupplicantCallFuncUnion fn, + void* data); + +typedef struct gsupplicant_call { + GSupplicant* supplicant; + GCancellable* cancel; + gulong cancel_id; + GSupplicantCallFinishFunc finish; + GSupplicantCallFuncUnion fn; + void* data; +} GSupplicantCall; + +/* Supplicant properties */ +#define GSUPPLICANT_PROPERTIES_(p) \ + p(VALID,valid) \ + p(CAPABILITIES,capabilities) \ + p(EAP_METHODS,eap-methods) \ + p(INTERFACES,interfaces) + +/* Supplicant signals */ +typedef enum gsupplicant_signal { +#define SIGNAL_ENUM_(P,p) SIGNAL_##P##_CHANGED, + GSUPPLICANT_PROPERTIES_(SIGNAL_ENUM_) +#undef SIGNAL_ENUM_ + SIGNAL_PROPERTY_CHANGED, + SIGNAL_COUNT +} GSUPPLICANT_SIGNAL; + +#define SIGNAL_BIT(name) (1 << SIGNAL_##name##_CHANGED) + +/* Assert that we have covered all publicly defined properties */ +G_STATIC_ASSERT((int)SIGNAL_PROPERTY_CHANGED == + ((int)GSUPPLICANT_PROPERTY_COUNT-1)); + +#define SIGNAL_PROPERTY_CHANGED_NAME "property-changed" +#define SIGNAL_PROPERTY_CHANGED_DETAIL "%x" +#define SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN (8) + +static GQuark gsupplicant_property_quarks[SIGNAL_PROPERTY_CHANGED]; +static guint gsupplicant_signals[SIGNAL_COUNT]; +static const char* gsupplicant_signame[SIGNAL_COUNT] = { +#define SIGNAL_NAME_(P,p) #p "-changed", + GSUPPLICANT_PROPERTIES_(SIGNAL_NAME_) +#undef SIGNAL_NAME_ + SIGNAL_PROPERTY_CHANGED_NAME +}; +G_STATIC_ASSERT(G_N_ELEMENTS(gsupplicant_signame) == SIGNAL_COUNT); + +/* Proxy properties */ +#define PROXY_PROPERTY_NAME_OWNER "g-name-owner" +#define PROXY_PROPERTY_NAME_DEBUG_LEVEL "DebugLevel" +#define PROXY_PROPERTY_NAME_DEBUG_SHOW_KEYS "DebugShowKeys" +#define PROXY_PROPERTY_NAME_DEBUG_TIMESTAMP "DebugTimestamp" +#define PROXY_PROPERTY_NAME_CAPABILITIES "Capabilities" +#define PROXY_PROPERTY_NAME_EAP_METHODS "EapMethods" +#define PROXY_PROPERTY_NAME_INTERFACES "Interfaces" + +/* Capabilities */ +static const GSupNameIntPair gsupplicant_caps [] = { + { "ap", GSUPPLICANT_CAPS_AP }, + { "ibss-rsn", GSUPPLICANT_CAPS_IBSS_RSN }, + { "p2p", GSUPPLICANT_CAPS_P2P }, + { "interworking", GSUPPLICANT_CAPS_INTERWORKING } +}; + +/* EAP methods */ +static const GSupNameIntPair gsupplicant_eap_methods [] = { + { "MD5", GSUPPLICANT_EAP_METHOD_MD5 }, + { "TLS", GSUPPLICANT_EAP_METHOD_TLS }, + { "MSCHAPV2", GSUPPLICANT_EAP_METHOD_MSCHAPV2 }, + { "PEAP", GSUPPLICANT_EAP_METHOD_PEAP }, + { "TTLS", GSUPPLICANT_EAP_METHOD_TTLS }, + { "GTC", GSUPPLICANT_EAP_METHOD_GTC }, + { "OTP", GSUPPLICANT_EAP_METHOD_OTP }, + { "SIM", GSUPPLICANT_EAP_METHOD_SIM }, + { "LEAP", GSUPPLICANT_EAP_METHOD_LEAP }, + { "PSK", GSUPPLICANT_EAP_METHOD_PSK }, + { "AKA", GSUPPLICANT_EAP_METHOD_AKA }, + { "FAST", GSUPPLICANT_EAP_METHOD_FAST }, + { "PAX", GSUPPLICANT_EAP_METHOD_PAX }, + { "SAKE", GSUPPLICANT_EAP_METHOD_SAKE }, + { "GPSK", GSUPPLICANT_EAP_METHOD_GPSK }, + { "WSC", GSUPPLICANT_EAP_METHOD_WSC }, + { "IKEV2", GSUPPLICANT_EAP_METHOD_IKEV2 }, + { "TNC", GSUPPLICANT_EAP_METHOD_TNC }, + { "PWD", GSUPPLICANT_EAP_METHOD_PWD } +}; + +static const GSupNameIntPair gsupplicant_cipher_suites [] = { + { "none", GSUPPLICANT_CIPHER_NONE }, + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP }, + { "wep104", GSUPPLICANT_CIPHER_WEP104 }, + { "wep40", GSUPPLICANT_CIPHER_WEP40 }, + { "aes128cmac", GSUPPLICANT_CIPHER_AES128_CMAC } +}; + +static const GSupNameIntPair gsupplicant_keymgmt_suites [] = { + { "none", GSUPPLICANT_KEYMGMT_NONE }, + { "wpa-psk", GSUPPLICANT_KEYMGMT_WPA_PSK }, + { "wpa-ft-psk", GSUPPLICANT_KEYMGMT_WPA_FT_PSK }, + { "wpa-psk-sha256", GSUPPLICANT_KEYMGMT_WPA_PSK_SHA256 }, + { "wpa-eap", GSUPPLICANT_KEYMGMT_WPA_EAP }, + { "wpa-ft-eap", GSUPPLICANT_KEYMGMT_WPA_FT_EAP }, + { "wpa-eap-sha256", GSUPPLICANT_KEYMGMT_WPA_EAP_SHA256 }, + { "ieee8021x", GSUPPLICANT_KEYMGMT_IEEE8021X }, + { "wpa-none", GSUPPLICANT_KEYMGMT_WPA_NONE }, + { "wps", GSUPPLICANT_KEYMGMT_WPS } +}; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +gsupplicant_call_cancelled( + GCancellable* cancel, + gpointer data) +{ + GSupplicantCall* call = data; + GASSERT(call->supplicant); + GASSERT(call->cancel == cancel); + gsupplicant_unref(call->supplicant); + call->supplicant = NULL; +} + +static +void +gsupplicant_call_finished( + GObject* proxy, + GAsyncResult* result, + gpointer data) +{ + GSupplicantCall* call = data; + g_cancellable_disconnect(call->cancel, call->cancel_id); + if (!g_cancellable_is_cancelled(call->cancel)) { + GASSERT(call->supplicant); + call->finish(call->supplicant, call->cancel, result, + call->fn, call->data); + } else { + /* Generic cleanup */ + GVariant* var = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), + result, NULL); + if (var) { + g_variant_unref(var); + } + } + gsupplicant_unref(call->supplicant); + g_object_unref(call->cancel); + g_slice_free(GSupplicantCall, call); +} + +static +GSupplicantCall* +gsupplicant_call_new( + GSupplicant* supplicant, + GSupplicantCallFinishFunc finish, + GCallback cb, + void* data) +{ + GSupplicantCall* call = g_slice_new0(GSupplicantCall); + call->cancel = g_cancellable_new(); + call->cancel_id = g_cancellable_connect(call->cancel, + G_CALLBACK(gsupplicant_call_cancelled), call, NULL); + call->supplicant = gsupplicant_ref(supplicant); + call->finish = finish; + call->fn.cb = cb; + call->data = data; + return call; +} + +static +void +gsupplicant_call_finish_void( + GSupplicant* supplicant, + GCancellable* cancel, + GAsyncResult* result, + GSupplicantCallFuncUnion fn, + void* data) +{ + GError* error = NULL; + GDBusProxy* proxy = G_DBUS_PROXY(supplicant->priv->proxy); + GVariant* var = g_dbus_proxy_call_finish(proxy, result, &error); + if (var) { + g_variant_unref(var); + } + if (fn.fn_void) { + fn.fn_void(supplicant, cancel, error, data); + } + if (error) { + g_error_free(error); + } +} + +static +void +gsupplicant_call_finish_string( + GSupplicant* supplicant, + GCancellable* cancel, + GAsyncResult* result, + gboolean (*get_result)( + FiW1Wpa_supplicant1* proxy, + gchar** str, + GAsyncResult* res, + GError** error), + GSupplicantStringResultFunc fn, + void* data) +{ + char* str = NULL; + GError* error = NULL; + GSupplicantPriv* priv = supplicant->priv; + get_result(priv->proxy, &str, result, &error); + if (fn) { + fn(supplicant, cancel, error, str, data); + } + if (error) { + g_error_free(error); + } + g_free(str); +} + +static +void +gsupplicant_call_finish_create_interface( + GSupplicant* supplicant, + GCancellable* cancel, + GAsyncResult* result, + GSupplicantCallFuncUnion fn, + void* data) +{ + gsupplicant_call_finish_string(supplicant, cancel, result, + fi_w1_wpa_supplicant1_call_create_interface_finish, fn.fn_string, data); +} + +static +void +gsupplicant_call_finish_get_interface( + GSupplicant* supplicant, + GCancellable* cancel, + GAsyncResult* result, + GSupplicantCallFuncUnion fn, + void* data) +{ + gsupplicant_call_finish_string(supplicant, cancel, result, + fi_w1_wpa_supplicant1_call_get_interface_finish, fn.fn_string, data); +} + +static inline +GSUPPLICANT_PROPERTY +gsupplicant_property_from_signal( + GSUPPLICANT_SIGNAL sig) +{ + switch (sig) { +#define SIGNAL_PROPERTY_MAP_(P,p) \ + case SIGNAL_##P##_CHANGED: return GSUPPLICANT_PROPERTY_##P; + GSUPPLICANT_PROPERTIES_(SIGNAL_PROPERTY_MAP_) +#undef SIGNAL_PROPERTY_MAP_ + default: /* unreachable */ return GSUPPLICANT_PROPERTY_ANY; + } +} + +static +void +gsupplicant_signal_property_change( + GSupplicant* self, + GSUPPLICANT_SIGNAL sig, + GSUPPLICANT_PROPERTY prop) +{ + GSupplicantPriv* priv = self->priv; + GASSERT(prop > GSUPPLICANT_PROPERTY_ANY); + GASSERT(prop < GSUPPLICANT_PROPERTY_COUNT); + GASSERT(sig < G_N_ELEMENTS(gsupplicant_property_quarks)); + if (!gsupplicant_property_quarks[sig]) { + char buf[SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN + 1]; + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_DETAIL, prop); + buf[sizeof(buf)-1] = 0; + gsupplicant_property_quarks[sig] = g_quark_from_string(buf); + } + priv->pending_signals &= ~(1 << sig); + g_signal_emit(self, gsupplicant_signals[sig], 0); + g_signal_emit(self, gsupplicant_signals[SIGNAL_PROPERTY_CHANGED], + gsupplicant_property_quarks[sig], prop); +} + +static +void +gsupplicant_emit_pending_signals( + GSupplicant* self) +{ + GSupplicantPriv* priv = self->priv; + GSUPPLICANT_SIGNAL sig; + gboolean valid_changed; + + /* Handlers could drops their references to us */ + gsupplicant_ref(self); + + /* VALID is the last one to be emitted if we BECOME valid */ + if ((priv->pending_signals & SIGNAL_BIT(VALID)) && self->valid) { + priv->pending_signals &= ~SIGNAL_BIT(VALID); + valid_changed = TRUE; + } else { + valid_changed = FALSE; + } + + /* Emit the signals. Not that in case if valid has become FALSE, then + * VALID is emitted first, otherwise it's emitted last */ + for (sig = SIGNAL_VALID_CHANGED; + sig < SIGNAL_PROPERTY_CHANGED && priv->pending_signals; + sig++) { + if (priv->pending_signals & (1 << sig)) { + gsupplicant_signal_property_change(self, sig, + gsupplicant_property_from_signal(sig)); + } + } + + /* Then emit VALID if valid has become TRUE */ + if (valid_changed) { + gsupplicant_signal_property_change(self, + SIGNAL_VALID_CHANGED, GSUPPLICANT_PROPERTY_VALID); + } + + /* Handlers could drops their references to us */ + gsupplicant_ref(self); +} + +static +guint +gsupplicant_convert_to_bitmask( + const gchar* const* values, + const GSupNameIntPair* list, + size_t count) +{ + guint mask = 0; + if (values) { + const gchar* const* name = values; + while (*name) { + const GSupNameIntPair* pair = + gsupplicant_name_int_find_name(*name, list, count); + if (pair) mask |= pair->value; + name++; + } + } + return mask; +} + +static +guint +gsupplicant_get_caps( + GSupplicant* self) +{ + return self->valid ? gsupplicant_convert_to_bitmask( + fi_w1_wpa_supplicant1_get_capabilities(self->priv->proxy), + gsupplicant_caps, G_N_ELEMENTS(gsupplicant_caps)) : 0; +} + +static +guint +gsupplicant_get_eap_methods( + GSupplicant* self) +{ + return self->valid ? gsupplicant_convert_to_bitmask( + fi_w1_wpa_supplicant1_get_eap_methods(self->priv->proxy), + gsupplicant_eap_methods, G_N_ELEMENTS(gsupplicant_eap_methods)) : 0; +} + +static +void +gsupplicant_update_interfaces( + GSupplicant* self) +{ + GSupplicantPriv* priv = self->priv; + /* + * g_variant_get_objv returns shallow copy, i.e. the return + * result should be released with g_free(), but the individual + * strings must not be modified. + */ + gchar** interfaces = (char**)(self->valid ? + fi_w1_wpa_supplicant1_get_interfaces(priv->proxy) : NULL); + if (!gutil_strv_equal((const GStrV*)interfaces, priv->interfaces)) { + GStrV* ptr; + for (ptr = interfaces; *ptr; ptr++) { + *ptr = g_strdup(*ptr); + } + g_strfreev(priv->interfaces); + self->interfaces = priv->interfaces = interfaces; + priv->pending_signals |= SIGNAL_BIT(INTERFACES); + } else { + g_free(interfaces); + } +} + +static +void +gsupplicant_update_caps( + GSupplicant* self) +{ + const guint caps = gsupplicant_get_caps(self); + if (self->caps != caps) { + self->caps = caps; + self->priv->pending_signals |= SIGNAL_BIT(CAPABILITIES); + } +} + +static +void +gsupplicant_update_eap_methods( + GSupplicant* self) +{ + const guint methods = gsupplicant_get_eap_methods(self); + if (self->eap_methods != methods) { + self->eap_methods = methods; + self->priv->pending_signals |= SIGNAL_BIT(EAP_METHODS); + } +} + +static +void +gsupplicant_update_name_owner( + GSupplicant* self) +{ + GSupplicantPriv* priv = self->priv; + GDBusProxy *proxy = G_DBUS_PROXY(priv->proxy); + char* owner = g_dbus_proxy_get_name_owner(proxy); + const gboolean was_valid = (priv->name_owner != NULL); + if (g_strcmp0(owner, priv->name_owner)) { + g_free(priv->name_owner); + priv->name_owner = owner; + self->valid = (owner != NULL); + gsupplicant_update_interfaces(self); + gsupplicant_update_caps(self); + gsupplicant_update_eap_methods(self); + if (self->valid) { + /* + * There won't be any cached properties if GetAll call failed + * due to permission or any other problem. + */ + gchar** names = g_dbus_proxy_get_cached_property_names(proxy); + self->failed = (!names); + g_strfreev(names); + } else { + self->failed = FALSE; + } + if (was_valid != self->valid) { + priv->pending_signals |= SIGNAL_BIT(VALID); + } + } else { + g_free(owner); + } +} + +static +void +gsupplicant_proxy_gproperties_changed( + GDBusProxy* proxy, + GVariant* changed, + GStrv invalidated, + gpointer data) +{ + GSupplicant* self = GSUPPLICANT(data); + GSupplicantPriv* priv = self->priv; + gboolean name_owner_changed = FALSE; + if (invalidated) { + char** ptr; + for (ptr = invalidated; *ptr; ptr++) { + const char* name = *ptr; + if (!strcmp(PROXY_PROPERTY_NAME_OWNER, name)) { + name_owner_changed = TRUE; + } else if (!strcmp(PROXY_PROPERTY_NAME_CAPABILITIES, name)) { + if (self->caps) { + self->caps = 0; + priv->pending_signals |= SIGNAL_BIT(CAPABILITIES); + } + } else if (!strcmp(PROXY_PROPERTY_NAME_EAP_METHODS, name)) { + if (self->eap_methods) { + self->eap_methods = 0; + priv->pending_signals |= SIGNAL_BIT(EAP_METHODS); + } + } else if (!strcmp(PROXY_PROPERTY_NAME_INTERFACES, name)) { + if (priv->interfaces) { + g_strfreev(priv->interfaces); + self->interfaces = priv->interfaces = NULL; + priv->pending_signals |= SIGNAL_BIT(INTERFACES); + } + } + } + } + if (changed) { + GVariantIter it; + GVariant* value; + const gchar* name; + g_variant_iter_init(&it, changed); + while (g_variant_iter_next(&it, "{&sv}", &name, &value)) { + if (!strcmp(PROXY_PROPERTY_NAME_OWNER, name)) { + name_owner_changed = TRUE; + } else if (!strcmp(PROXY_PROPERTY_NAME_CAPABILITIES, name)) { + gsupplicant_update_caps(self); + } else if (!strcmp(PROXY_PROPERTY_NAME_EAP_METHODS, name)) { + gsupplicant_update_eap_methods(self); + } else if (!strcmp(PROXY_PROPERTY_NAME_INTERFACES, name)) { + gsupplicant_update_interfaces(self); + } + g_variant_unref(value); + } + } + + if (name_owner_changed) { + gsupplicant_update_name_owner(self); + } + + gsupplicant_emit_pending_signals(self); +} + +static +void +gsupplicant_proxy_properties_changed( + GDBusProxy* proxy, + GVariant* changed, + gpointer data) +{ + gsupplicant_proxy_gproperties_changed(proxy, changed, NULL, data); +} + +static +void +gsupplicant_proxy_interface_added( + GDBusProxy* proxy, + const char* path, + GVariant* properties, + gpointer data) +{ + GSupplicant* self = GSUPPLICANT(data); + GSupplicantPriv* priv = self->priv; + GDEBUG("Interface added: %s", path); + if (!gutil_strv_contains(priv->interfaces, path)) { + self->interfaces = priv->interfaces = + gutil_strv_add(priv->interfaces, path); + priv->pending_signals |= SIGNAL_BIT(INTERFACES); + gsupplicant_emit_pending_signals(self); + } +} + +static +void +gsupplicant_proxy_interface_removed( + GDBusProxy* proxy, + const char* path, + gpointer data) +{ + GSupplicant* self = GSUPPLICANT(data); + GSupplicantPriv* priv = self->priv; + const int pos = gutil_strv_find(priv->interfaces, path); + GDEBUG("Interface removed: %s", path); + if (pos >= 0) { + self->interfaces = priv->interfaces = + gutil_strv_remove_at(priv->interfaces, pos, TRUE); + priv->pending_signals |= SIGNAL_BIT(INTERFACES); + gsupplicant_emit_pending_signals(self); + } +} + +static +void +gsupplicant_proxy_created( + GObject* bus, + GAsyncResult* result, + gpointer data) +{ + GSupplicant* self = GSUPPLICANT(data); + GSupplicantPriv* priv = self->priv; + GError* error = NULL; + FiW1Wpa_supplicant1* proxy = fi_w1_wpa_supplicant1_proxy_new_finish( + result, &error); + if (proxy) { + GASSERT(!priv->proxy); + GASSERT(!priv->name_owner); + GASSERT(!self->valid); + + priv->proxy = proxy; + priv->proxy_handler_id[PROXY_GPROPERTIES_CHANGED] = + g_signal_connect(proxy, "g-properties-changed", + G_CALLBACK(gsupplicant_proxy_gproperties_changed), self); + priv->proxy_handler_id[PROXY_PROPERTIES_CHANGED] = + g_signal_connect(proxy, "properties-changed", + G_CALLBACK(gsupplicant_proxy_properties_changed), self); + priv->proxy_handler_id[PROXY_INTERFACE_ADDED] = + g_signal_connect(proxy, "interface-added", + G_CALLBACK(gsupplicant_proxy_interface_added), self); + priv->proxy_handler_id[PROXY_INTERFACE_REMOVED] = + g_signal_connect(proxy, "interface-removed", + G_CALLBACK(gsupplicant_proxy_interface_removed), self); + + gsupplicant_update_name_owner(self); + gsupplicant_emit_pending_signals(self); + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + gsupplicant_unref(self); +} + +static +void +gsupplicant_bus_get_finished( + GObject* object, + GAsyncResult* result, + gpointer data) +{ + GSupplicant* self = GSUPPLICANT(data); + GSupplicantPriv* priv = self->priv; + GError* error = NULL; + priv->bus = g_bus_get_finish(result, &error); + if (priv->bus) { + fi_w1_wpa_supplicant1_proxy_new(priv->bus, G_DBUS_PROXY_FLAGS_NONE, + GSUPPLICANT_SERVICE, GSUPPLICANT_PATH, NULL, + gsupplicant_proxy_created, gsupplicant_ref(self)); + } else { + GERR("Failed to attach to system bus: %s", GERRMSG(error)); + g_error_free(error); + } + gsupplicant_unref(self); +} + +/*==========================================================================* + * API + *==========================================================================*/ + +GSupplicant* +gsupplicant_new() +{ + /* Weak reference to the single instance of GSupplicant */ + static GSupplicant* gsupplicant_instance = NULL; + if (gsupplicant_instance) { + gsupplicant_ref(gsupplicant_instance); + } else { + gsupplicant_instance = g_object_new(GSUPPLICANT_TYPE, NULL); + g_bus_get(GSUPPLICANT_BUS_TYPE, NULL, gsupplicant_bus_get_finished, + gsupplicant_ref(gsupplicant_instance)); + g_object_add_weak_pointer(G_OBJECT(gsupplicant_instance), + (gpointer*)(&gsupplicant_instance)); + } + return gsupplicant_instance; +} + +GSupplicant* +gsupplicant_ref( + GSupplicant* self) +{ + if (G_LIKELY(self)) { + g_object_ref(GSUPPLICANT(self)); + return self; + } else { + return NULL; + } +} + +void +gsupplicant_unref( + GSupplicant* self) +{ + if (G_LIKELY(self)) { + g_object_unref(GSUPPLICANT(self)); + } +} + +gulong +gsupplicant_add_property_changed_handler( + GSupplicant* self, + GSUPPLICANT_PROPERTY property, + GSupplicantPropertyFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signal_name; + char buf[sizeof(SIGNAL_PROPERTY_CHANGED_NAME) + 2 + + SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN]; + if (property) { + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_NAME "::" + SIGNAL_PROPERTY_CHANGED_DETAIL, property); + buf[sizeof(buf)-1] = 0; + signal_name = buf; + } else { + signal_name = SIGNAL_PROPERTY_CHANGED_NAME; + } + return g_signal_connect(self, signal_name, G_CALLBACK(fn), data); + } + return 0; +} + +gulong +gsupplicant_add_handler( + GSupplicant* self, + GSUPPLICANT_PROPERTY prop, + GSupplicantFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signame; + switch (prop) { +#define SIGNAL_NAME_(P,p) case GSUPPLICANT_PROPERTY_##P: \ + signame = gsupplicant_signame[SIGNAL_##P##_CHANGED]; \ + break; + GSUPPLICANT_PROPERTIES_(SIGNAL_NAME_) + default: + signame = NULL; + break; + } + if (G_LIKELY(signame)) { + return g_signal_connect(self, signame, G_CALLBACK(fn), data); + } + } + return 0; +} + +void +gsupplicant_remove_handler( + GSupplicant* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +gsupplicant_remove_handlers( + GSupplicant* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +GCancellable* +gsupplicant_create_interface( + GSupplicant* self, + const GSupplicantCreateInterfaceParams* params, + GSupplicantStringResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid && params && params->ifname) { + GSupplicantPriv* priv = self->priv; + GSupplicantCall* call = gsupplicant_call_new(self, + gsupplicant_call_finish_create_interface, G_CALLBACK(fn), data); + GVariantBuilder builder; + GVariant* dict; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + gsupplicant_dict_add_string(&builder, "Ifname", params->ifname); + gsupplicant_dict_add_string0(&builder, "BridgeIfname", + params->bridge_ifname); + gsupplicant_dict_add_string0(&builder, "Driver", params->driver); + gsupplicant_dict_add_string0(&builder, "ConfigFile", + params->config_file); + dict = g_variant_ref_sink(g_variant_builder_end(&builder)); + fi_w1_wpa_supplicant1_call_create_interface(priv->proxy, dict, + call->cancel, gsupplicant_call_finished, call); + g_variant_unref(dict); + return call->cancel; + } + return NULL; +} + +GCancellable* +gsupplicant_remove_interface( + GSupplicant* self, + const char* path, + GSupplicantResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid && + path && g_variant_is_object_path(path)) { + GSupplicantPriv* priv = self->priv; + GSupplicantCall* call = gsupplicant_call_new(self, + gsupplicant_call_finish_void, G_CALLBACK(fn), data); + fi_w1_wpa_supplicant1_call_remove_interface(priv->proxy, path, + call->cancel, gsupplicant_call_finished, call); + return call->cancel; + } + return NULL; +} + +GCancellable* +gsupplicant_get_interface( + GSupplicant* self, + const char* ifname, + GSupplicantStringResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantPriv* priv = self->priv; + GSupplicantCall* call = gsupplicant_call_new(self, + gsupplicant_call_finish_get_interface, G_CALLBACK(fn), data); + fi_w1_wpa_supplicant1_call_get_interface(priv->proxy, ifname, + call->cancel, gsupplicant_call_finished, call); + return call->cancel; + } + return NULL; +} + +const char* +gsupplicant_caps_name( + guint caps, + guint* cap) +{ + return gsupplicant_name_int_find_bit(caps, cap, + gsupplicant_caps, G_N_ELEMENTS(gsupplicant_caps)); +} + +const char* +gsupplicant_eap_method_name( + guint methods, + guint* method) +{ + return gsupplicant_name_int_find_bit(methods, method, + gsupplicant_eap_methods, G_N_ELEMENTS(gsupplicant_eap_methods)); +} + +const char* +gsupplicant_cipher_suite_name( + guint cipher_suites, + guint* cipher_suite) +{ + return gsupplicant_name_int_find_bit(cipher_suites, cipher_suite, + gsupplicant_cipher_suites, G_N_ELEMENTS(gsupplicant_cipher_suites)); +} + +const char* +gsupplicant_keymgmt_suite_name( + guint keymgmt_suites, + guint* keymgmt_suite) +{ + return gsupplicant_name_int_find_bit(keymgmt_suites, keymgmt_suite, + gsupplicant_keymgmt_suites, G_N_ELEMENTS(gsupplicant_keymgmt_suites)); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +/** + * Per instance initializer + */ +static +void +gsupplicant_init( + GSupplicant* self) +{ + GSupplicantPriv* priv = G_TYPE_INSTANCE_GET_PRIVATE(self, + GSUPPLICANT_TYPE, GSupplicantPriv); + self->priv = priv; +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +gsupplicant_dispose( + GObject* object) +{ + GSupplicant* self = GSUPPLICANT(object); + GSupplicantPriv* priv = self->priv; + if (priv->proxy) { + gutil_disconnect_handlers(priv->proxy, priv->proxy_handler_id, + G_N_ELEMENTS(priv->proxy_handler_id)); + g_object_unref(priv->proxy); + priv->proxy = NULL; + } + if (priv->bus) { + g_object_unref(priv->bus); + priv->bus = NULL; + } + G_OBJECT_CLASS(gsupplicant_parent_class)->dispose(object); +} + +/** + * Final stage of deinitialization + */ +static +void +gsupplicant_finalize( + GObject* object) +{ + GSupplicant* self = GSUPPLICANT(object); + GSupplicantPriv* priv = self->priv; + GVERBOSE_(""); + GASSERT(!priv->proxy); + if (priv->bus) { + g_dbus_connection_flush_sync(priv->bus, NULL, NULL); + g_object_unref(priv->bus); + } + g_free(priv->name_owner); + g_strfreev(priv->interfaces); + G_OBJECT_CLASS(gsupplicant_parent_class)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +gsupplicant_class_init( + GSupplicantClass* klass) +{ + int i; + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = gsupplicant_dispose; + object_class->finalize = gsupplicant_finalize; + g_type_class_add_private(klass, sizeof(GSupplicantPriv)); + for (i=0; i + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant_bss.h" +#include "gsupplicant_interface.h" +#include "gsupplicant_util_p.h" +#include "gsupplicant_dbus.h" +#include "gsupplicant_log.h" + +#include +#include + +#include + +/* Generated headers */ +#include "fi.w1.wpa_supplicant1.BSS.h" + +/* Object definition */ +enum supplicant_bss_proxy_handler_id { + PROXY_GPROPERTIES_CHANGED, + PROXY_PROPERTIES_CHANGED, + PROXY_HANDLER_COUNT +}; + +enum supplicant_iface_handler_id { + INTERFACE_VALID_CHANGED, + INTERFACE_BSSS_CHANGED, + INTERFACE_HANDLER_COUNT +}; + +typedef struct gsupplicant_bss_connect_data { + GSupplicantBSS* bss; + GSupplicantBSSStringResultFunc fn; + void* fn_data; +} GSupplicantBSSConnectData; + +struct gsupplicant_bss_priv { + FiW1Wpa_supplicant1BSS* proxy; + gulong proxy_handler_id[PROXY_HANDLER_COUNT]; + gulong iface_handler_id[INTERFACE_HANDLER_COUNT]; + char* path; + char* ssid_str; + GSupplicantBSSWPA wpa; + GSupplicantBSSRSN rsn; + GSupplicantUIntArray rates; + guint* rates_values; + guint32 pending_signals; +}; + +typedef enum wps_methods { + WPS_METHODS_NONE = (0x00000000), + WPS_METHODS_PIN = (0x00000001), + WPS_METHODS_BUTTON = (0x00000002) +} WPS_METHODS; + +typedef struct gsupplicant_wps_info { + guint flags; + +#define WPS_INFO_VERSION (0x0001) +#define WPS_INFO_STATE (0x0002) +#define WPS_INFO_METHODS (0x0004) +#define WPS_INFO_REGISTRAR (0x0008) +#define WPS_INFO_REQUIRED (WPS_INFO_VERSION | WPS_INFO_STATE) + + guint32 version; + guint32 state; + guint32 registrar; + WPS_METHODS methods; +} GSupplicantWPSInfo; + +#define WPS_TLV_VERSION 0x104a +#define WPS_TLV_STATE 0x1044 +#define WPS_TLV_METHOD 0x1012 +#define WPS_TLV_REGISTRAR 0x1041 +#define WPS_TLV_DEVICENAME 0x1011 +#define WPS_TLV_UUID 0x1047 + +#define WMM_WPA1_WPS_INFO 0xdd +#define WMM_WPA1_WPS_OUI 0x00,0x50,0xf2,0x04 +#define WPS_VERSION 0x10 +#define WPS_METHOD_PUSH_BUTTON 0x04 +#define WPS_METHOD_PIN 0x00 +#define WPS_STATE_CONFIGURED 0x02 + +typedef GObjectClass GSupplicantBSSClass; +G_DEFINE_TYPE(GSupplicantBSS, gsupplicant_bss, G_TYPE_OBJECT) +#define GSUPPLICANT_BSS_TYPE (gsupplicant_bss_get_type()) +#define GSUPPLICANT_BSS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GSUPPLICANT_BSS_TYPE, GSupplicantBSS)) +#define SUPER_CLASS gsupplicant_bss_parent_class + +/* BSS properties */ +#define GSUPPLICANT_BSS_PROPERTIES_(p) \ + p(VALID,valid) \ + p(PRESENT,present) \ + p(SSID,ssid) \ + p(BSSID,bssid) \ + p(WPA,wpa) \ + p(RSN,rsn) \ + p(MODE,mode) \ + p(WPS_CAPS,wpscaps) \ + p(IES,ies) \ + p(PRIVACY,privacy) \ + p(FREQUENCY,frequency) \ + p(RATES,rates) \ + p(MAXRATE,maxrate) \ + p(SIGNAL,signal) + +typedef enum gsupplicant_bss_signal { +#define SIGNAL_ENUM_(P,p) SIGNAL_##P##_CHANGED, + GSUPPLICANT_BSS_PROPERTIES_(SIGNAL_ENUM_) +#undef SIGNAL_ENUM_ + SIGNAL_PROPERTY_CHANGED, + SIGNAL_COUNT +} GSUPPLICANT_BSS_SIGNAL; + +#define SIGNAL_BIT(name) (1 << SIGNAL_##name##_CHANGED) + +/* + * The code assumes that VALID is the first one and that their number + * doesn't exceed the number of bits in pending_signals (currently, 32) + */ +G_STATIC_ASSERT(SIGNAL_VALID_CHANGED == 0); +G_STATIC_ASSERT(SIGNAL_PROPERTY_CHANGED <= 32); + +/* Assert that we have covered all publicly defined properties */ +G_STATIC_ASSERT((int)SIGNAL_PROPERTY_CHANGED == + ((int)GSUPPLICANT_BSS_PROPERTY_COUNT-1)); + +#define SIGNAL_PROPERTY_CHANGED_NAME "property-changed" +#define SIGNAL_PROPERTY_CHANGED_DETAIL "%x" +#define SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN (8) + +static GQuark gsupplicant_bss_property_quarks[SIGNAL_PROPERTY_CHANGED]; +static guint gsupplicant_bss_signals[SIGNAL_COUNT]; +static const char* gsupplicant_bss_signame[] = { +#define SIGNAL_NAME_(P,p) #p "-changed", + GSUPPLICANT_BSS_PROPERTIES_(SIGNAL_NAME_) +#undef SIGNAL_NAME_ + SIGNAL_PROPERTY_CHANGED_NAME +}; +G_STATIC_ASSERT(G_N_ELEMENTS(gsupplicant_bss_signame) == SIGNAL_COUNT); + +/* Proxy properties */ +#define PROXY_PROPERTY_NAME_SSID "SSID" +#define PROXY_PROPERTY_NAME_BSSID "BSSID" +#define PROXY_PROPERTY_NAME_WPA "WPA" +#define PROXY_PROPERTY_NAME_RSN "RSN" +#define PROXY_PROPERTY_NAME_WPS "WPS" +#define PROXY_PROPERTY_NAME_IES "IEs" +#define PROXY_PROPERTY_NAME_PRIVACY "Privacy" +#define PROXY_PROPERTY_NAME_MODE "Mode" +#define PROXY_PROPERTY_NAME_SIGNAL "Signal" +#define PROXY_PROPERTY_NAME_FREQUENCY "Frequency" +#define PROXY_PROPERTY_NAME_RATES "Rates" + +/* Weak references to the instances of GSupplicantBSS */ +static GHashTable* gsupplicant_bss_table = NULL; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +gsupplicant_bss_connect_data_free( + GSupplicantBSSConnectData* data) +{ + gsupplicant_bss_unref(data->bss); + g_slice_free(GSupplicantBSSConnectData, data); +} + +static +GSupplicantBSSConnectData* +gsupplicant_bss_connect_data_new( + GSupplicantBSS* bss, + GSupplicantBSSStringResultFunc fn, + void* fn_data) +{ + GSupplicantBSSConnectData* data = g_slice_new0(GSupplicantBSSConnectData); + data->fn = fn; + data->fn_data = fn_data; + data->bss = gsupplicant_bss_ref(bss); + return data; +} + +static +void +gsupplicant_bss_connect_free( + gpointer data) +{ + gsupplicant_bss_connect_data_free(data); +} + +static +void +gsupplicant_bss_connect_done( + GSupplicantInterface* iface, + GCancellable* cancel, + const GError* error, + const char* result, + void* data) +{ + GSupplicantBSSConnectData* cp = data; + cp->fn(cp->bss, cancel, error, result, cp->fn_data); +} + +static +void +gsupplicant_bss_fill_network_params( + GSupplicantBSS* bss, + const GSupplicantBSSConnectParams* cp, + guint flags, /* None defined yet */ + GSupplicantNetworkParams* np) +{ + memset(np, 0, sizeof(*np)); + /* + * Ignore BSS frequency. It is ignored in the infrastructure + * mode anyway. It's only used by the station that creates the + * IBSS (adhoc network). If an IBSS network with the configured + * SSID is already present, the frequency of the network will be + * used instead of the configured value. + * + np->frequency = bss->frequency; + */ + np->ssid = bss->ssid; + np->mode = (bss->mode == GSUPPLICANT_BSS_MODE_AD_HOC) ? + GSUPPLICANT_OP_MODE_IBSS : GSUPPLICANT_OP_MODE_INFRA; + np->security = gsupplicant_bss_security(bss); + np->scan_ssid = 1; + np->eap = cp->eap; + np->auth_flags = cp->auth_flags; + np->bgscan = cp->bgscan; + np->passphrase = cp->passphrase; + np->identity = cp->identity; + np->anonymous_identity = cp->anonymous_identity; + np->ca_cert_file = cp->ca_cert_file; + np->client_cert_file = cp->client_cert_file; + np->private_key_file = cp->private_key_file; + np->private_key_passphrase = cp->private_key_passphrase; + np->subject_match = cp->subject_match; + np->altsubject_match = cp->altsubject_match; + np->domain_suffix_match = cp->domain_suffix_match; + np->domain_match = cp->domain_match; + np->phase2 = cp->phase2; + np->ca_cert_file2 = cp->ca_cert_file2; + np->client_cert_file2 = cp->client_cert_file2; + np->private_key_file2 = cp->private_key_file2; + np->private_key_passphrase2 = cp->private_key_passphrase2; + np->subject_match2 = cp->subject_match2; + np->altsubject_match2 = cp->altsubject_match2; + np->domain_suffix_match2 = cp->domain_suffix_match2; +} + +static inline +GSUPPLICANT_BSS_PROPERTY +gsupplicant_bss_property_from_signal( + GSUPPLICANT_BSS_SIGNAL sig) +{ + switch (sig) { +#define SIGNAL_PROPERTY_MAP_(P,p) \ + case SIGNAL_##P##_CHANGED: return GSUPPLICANT_BSS_PROPERTY_##P; + GSUPPLICANT_BSS_PROPERTIES_(SIGNAL_PROPERTY_MAP_) +#undef SIGNAL_PROPERTY_MAP_ + default: /* unreachable */ return GSUPPLICANT_BSS_PROPERTY_ANY; + } +} + +static +void +gsupplicant_bss_signal_property_change( + GSupplicantBSS* self, + GSUPPLICANT_BSS_SIGNAL sig, + GSUPPLICANT_BSS_PROPERTY prop) +{ + GSupplicantBSSPriv* priv = self->priv; + GASSERT(prop > GSUPPLICANT_BSS_PROPERTY_ANY); + GASSERT(prop < GSUPPLICANT_BSS_PROPERTY_COUNT); + GASSERT(sig < G_N_ELEMENTS(gsupplicant_bss_property_quarks)); + if (!gsupplicant_bss_property_quarks[sig]) { + char buf[SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN + 1]; + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_DETAIL, prop); + buf[sizeof(buf)-1] = 0; + gsupplicant_bss_property_quarks[sig] = g_quark_from_string(buf); + } + priv->pending_signals &= ~(1 << sig); + g_signal_emit(self, gsupplicant_bss_signals[sig], 0); + g_signal_emit(self, gsupplicant_bss_signals[SIGNAL_PROPERTY_CHANGED], + gsupplicant_bss_property_quarks[sig], prop); +} + +static +void +gsupplicant_bss_emit_pending_signals( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + GSUPPLICANT_BSS_SIGNAL sig; + gboolean valid_changed; + + /* Handlers could drops their references to us */ + gsupplicant_bss_ref(self); + + /* VALID is the last one to be emitted if we BECOME valid */ + if ((priv->pending_signals & SIGNAL_BIT(VALID)) && self->valid) { + priv->pending_signals &= ~SIGNAL_BIT(VALID); + valid_changed = TRUE; + } else { + valid_changed = FALSE; + } + + /* Emit the signals. Not that in case if valid has become FALSE, then + * VALID is emitted first, otherwise it's emitted last */ + for (sig = SIGNAL_VALID_CHANGED; + sig < SIGNAL_PROPERTY_CHANGED && priv->pending_signals; + sig++) { + if (priv->pending_signals & (1 << sig)) { + gsupplicant_bss_signal_property_change(self, sig, + gsupplicant_bss_property_from_signal(sig)); + } + } + + /* Then emit VALID if valid has become TRUE */ + if (valid_changed) { + gsupplicant_bss_signal_property_change(self, SIGNAL_VALID_CHANGED, + GSUPPLICANT_BSS_PROPERTY_VALID); + } + + /* And release the temporary reference */ + gsupplicant_bss_unref(self); +} + +static +gboolean +gsupplicant_bss_bytes_equal( + GBytes* b1, + GBytes* b2) +{ + if (b1 && b2) { + return g_bytes_equal(b1, b2); + } else { + return (!b1 && !b2); + } +} + +static +GBytes* +gsupplicant_bss_get_bytes( + GSupplicantBSS* self, + const char* name) +{ + GSupplicantBSSPriv* priv = self->priv; + if (priv->proxy) { + GDBusProxy *proxy = G_DBUS_PROXY(priv->proxy); + GVariant* var = g_dbus_proxy_get_cached_property(proxy, name); + if (var && g_variant_is_of_type(var, G_VARIANT_TYPE_BYTESTRING)) { + GBytes* bytes = g_variant_get_data_as_bytes(var); + g_variant_unref(var); + return bytes; + } + } + return NULL; +} + +static +void +gsupplicant_bss_update_valid( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + const gboolean valid = priv->proxy && self->iface->valid; + if (self->valid != valid) { + self->valid = valid; + GDEBUG("BSS %s is %svalid", priv->path, valid ? "" : "in"); + priv->pending_signals |= SIGNAL_BIT(VALID); + } +} + +static +void +gsupplicant_bss_update_present( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + const gboolean present = priv->proxy && self->iface->valid && + gutil_strv_contains(self->iface->bsss, priv->path); + if (self->present != present) { + self->present = present; + GDEBUG("BSS %s is %spresent", priv->path, present ? "" : "not "); + priv->pending_signals |= SIGNAL_BIT(PRESENT); + } +} + +static +void +gsupplicant_bss_update_ssid( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + GBytes* ssid = gsupplicant_bss_get_bytes(self, PROXY_PROPERTY_NAME_SSID); + if (!gsupplicant_bss_bytes_equal(self->ssid, ssid)) { + if (self->ssid) { + g_bytes_unref(self->ssid); + } + g_free(priv->ssid_str); + if (ssid) { + gsize size = 0; + const void* data = g_bytes_get_data(ssid, &size); + priv->ssid_str = g_malloc(size + 1); + memcpy(priv->ssid_str, data, size); + priv->ssid_str[size] = 0; + GDEBUG("[%s] " PROXY_PROPERTY_NAME_SSID ": %s \"%s\"", + self->path, gsupplicant_format_bytes(ssid, FALSE), + priv->ssid_str); + } else { + priv->ssid_str = NULL; + GDEBUG("[%s] " PROXY_PROPERTY_NAME_SSID ": %s", + self->path, gsupplicant_format_bytes(ssid, FALSE)); + } + self->ssid_str = priv->ssid_str; + self->ssid = ssid; + priv->pending_signals |= SIGNAL_BIT(SSID); + } else if (ssid) { + g_bytes_unref(ssid); + } +} + +static +void +gsupplicant_bss_update_bssid( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + GBytes* bssid = gsupplicant_bss_get_bytes(self, PROXY_PROPERTY_NAME_BSSID); + if (!gsupplicant_bss_bytes_equal(self->bssid, bssid)) { + if (self->bssid) { + g_bytes_unref(self->bssid); + } + self->bssid = bssid; + GDEBUG("[%s] " PROXY_PROPERTY_NAME_BSSID ": %s", + self->path, gsupplicant_format_bytes(bssid, FALSE)); + priv->pending_signals |= SIGNAL_BIT(BSSID); + } else if (bssid) { + g_bytes_unref(bssid); + } +} + +static +void +gsupplicant_bss_parse_wpa( + const char* name, + GVariant* value, + void* data) +{ + GSupplicantBSSWPA* wpa = data; + if (!g_strcmp0(name, "KeyMgmt")) { + static const GSupNameIntPair keymgmt_map [] = { + { "wpa-psk", GSUPPLICANT_KEYMGMT_WPA_PSK }, + { "wpa-eap", GSUPPLICANT_KEYMGMT_WPA_EAP }, + { "wpa-none", GSUPPLICANT_KEYMGMT_WPA_NONE } + }; + wpa->keymgmt = gsupplicant_parse_bits_array(0, name, value, + keymgmt_map, G_N_ELEMENTS(keymgmt_map)); + } else if (!g_strcmp0(name, "Pairwise")) { + static const GSupNameIntPair pairwise_map [] = { + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP } + }; + wpa->pairwise = gsupplicant_parse_bits_array(0, name, value, + pairwise_map, G_N_ELEMENTS(pairwise_map)); + } else if (!g_strcmp0(name, "Group")) { + static const GSupNameIntPair group_map [] = { + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP }, + { "wep104", GSUPPLICANT_CIPHER_WEP104 }, + { "wep40", GSUPPLICANT_CIPHER_WEP40 } + }; + GASSERT(g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)); + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + const char* str = g_variant_get_string(value, NULL); + const GSupNameIntPair* pair = gsupplicant_name_int_find_name(str, + group_map, G_N_ELEMENTS(group_map)); + if (pair) { + GVERBOSE(" %s: %s", name, str); + wpa->group = pair->value; + } + } + } else { + GWARN("Unexpected WPA dictionary key %s", name); + } +} + +static +void +gsupplicant_bss_update_wpa( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + const GSupplicantBSSWPA wpa = priv->wpa; + GVariant* dict; + memset(&priv->wpa, 0, sizeof(priv->wpa)); + GVERBOSE("[%s] WPA:", self->path); + dict = fi_w1_wpa_supplicant1_bss_get_wpa(priv->proxy); + gsupplicant_dict_parse(dict, gsupplicant_bss_parse_wpa, &priv->wpa); + if (dict) { + if (self->wpa) { + if (memcmp(&wpa, &priv->wpa, sizeof(wpa))) { + priv->pending_signals |= SIGNAL_BIT(WPA); + } + } else { + priv->pending_signals |= SIGNAL_BIT(WPA); + } + self->wpa = &priv->wpa; + } else if (self->wpa) { + self->wpa = NULL; + priv->pending_signals |= SIGNAL_BIT(WPA); + } +} + +static +void +gsupplicant_bss_parse_rsn( + const char* name, + GVariant* value, + void* data) +{ + GSupplicantBSSRSN* rsn = data; + if (!g_strcmp0(name, "KeyMgmt")) { + static const GSupNameIntPair keymgmt_map [] = { + { "wpa-psk", GSUPPLICANT_KEYMGMT_WPA_PSK }, + { "wpa-eap", GSUPPLICANT_KEYMGMT_WPA_EAP }, + { "wpa-ft-psk", GSUPPLICANT_KEYMGMT_WPA_FT_PSK }, + { "wpa-ft-eap", GSUPPLICANT_KEYMGMT_WPA_FT_EAP }, + { "wpa-psk-sha256", GSUPPLICANT_KEYMGMT_WPA_PSK_SHA256 }, + { "wpa-eap-sha256", GSUPPLICANT_KEYMGMT_WPA_EAP_SHA256 }, + }; + rsn->keymgmt = gsupplicant_parse_bits_array(0, name, value, + keymgmt_map, G_N_ELEMENTS(keymgmt_map)); + } else if (!g_strcmp0(name, "Pairwise")) { + static const GSupNameIntPair pairwise_map [] = { + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP } + }; + rsn->pairwise = gsupplicant_parse_bits_array(0, name, value, + pairwise_map, G_N_ELEMENTS(pairwise_map)); + } else if (!g_strcmp0(name, "Group")) { + static const GSupNameIntPair group_map [] = { + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP }, + { "wep104", GSUPPLICANT_CIPHER_WEP104 }, + { "wep40", GSUPPLICANT_CIPHER_WEP40 } + }; + GASSERT(g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)); + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + const char* str = g_variant_get_string(value, NULL); + const GSupNameIntPair* pair = gsupplicant_name_int_find_name(str, + group_map, G_N_ELEMENTS(group_map)); + if (pair) { + GVERBOSE(" %s: %s", name, str); + rsn->group = pair->value; + } + } + } else if (!g_strcmp0(name, "MgmtGroup")) { + static const GSupNameIntPair mgmt_group_map [] = { + { "aes128cmac", GSUPPLICANT_CIPHER_AES128_CMAC } + }; + GASSERT(g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)); + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + const char* str = g_variant_get_string(value, NULL); + const GSupNameIntPair* pair = gsupplicant_name_int_find_name(str, + mgmt_group_map, G_N_ELEMENTS(mgmt_group_map)); + if (pair) { + GVERBOSE(" %s: %s", name, str); + rsn->mgmt_group = pair->value; + } + } + } else { + GWARN("Unexpected RSN dictionary key %s", name); + } +} + +static +void +gsupplicant_bss_update_rsn( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + const GSupplicantBSSRSN rsn = priv->rsn; + GVariant* dict; + memset(&priv->rsn, 0, sizeof(priv->rsn)); + GVERBOSE("[%s] RSN:", self->path); + dict = fi_w1_wpa_supplicant1_bss_get_rsn(priv->proxy); + gsupplicant_dict_parse(dict, gsupplicant_bss_parse_rsn, &priv->rsn); + if (dict) { + if (self->rsn) { + if (memcmp(&rsn, &priv->rsn, sizeof(rsn))) { + priv->pending_signals |= SIGNAL_BIT(RSN); + } + } else { + priv->pending_signals |= SIGNAL_BIT(RSN); + } + self->rsn = &priv->rsn; + } else if (self->rsn) { + self->rsn = NULL; + priv->pending_signals |= SIGNAL_BIT(RSN); + } +} + +#if GUTIL_LOG_VERBOSE +static +const char* +gsupplicant_bss_wps_oui_type_name( + guint type) +{ + switch (type) { + case WPS_TLV_VERSION: return "version"; + case WPS_TLV_STATE: return "state"; + case WPS_TLV_METHOD: return "method"; + case WPS_TLV_REGISTRAR: return "registrar"; + default: return NULL; + } +} +#endif /* GUTIL_LOG_VERBOSE */ + +static +gboolean +gsupplicant_bss_parse_wps_oui( + const guint8* ie, + guint len, + GSupplicantWPSInfo* wps) +{ + const guint8* end = ie + len; + memset(wps, 0, sizeof(*wps)); + while (ie + 4 <= end) { + /* Parse and skip the header */ + const guint v_type = (ie[0] << 8) + ie[1]; + const guint v_len = (ie[2] << 8) + ie[3]; + ie += 4; + + /* The data */ + if (v_len <= 4 && ie + v_len <= end) { + guint32* data; + guint32 tmp; + guint flag; + switch (v_type) { + case WPS_TLV_VERSION: + flag = WPS_INFO_VERSION; + data = &wps->version; + break; + case WPS_TLV_STATE: + flag = WPS_INFO_STATE; + data = &wps->state; + break; + case WPS_TLV_METHOD: + flag = WPS_INFO_METHODS; + data = &tmp; + break; + case WPS_TLV_REGISTRAR: + flag = WPS_INFO_REGISTRAR; + data = &wps->registrar; + break; + default: + data = NULL; + break; + } + if (data) { + guint i; + *data = 0; + for (i=0; iflags |= flag; + GVERBOSE_("0x%04x (%s): 0x%02x", v_type, + gsupplicant_bss_wps_oui_type_name(v_type), *data); + if (v_type == WPS_TLV_METHOD) { + switch (tmp) { + case WPS_METHOD_PIN: + wps->methods |= WPS_METHODS_PIN; + break; + case WPS_METHOD_PUSH_BUTTON: + wps->methods |= WPS_METHODS_BUTTON; + break; + default: + break; + } + } + } + } + + /* Advance to the next element */ + ie += v_len; + } + GASSERT(ie == end); + return (ie == end); +} + +static +GSUPPLICANT_WPS_CAPS +gsupplicant_bss_parse_ies( + GBytes* ies) +{ + GSUPPLICANT_WPS_CAPS wps_caps = GSUPPLICANT_WPS_NONE; + gsize len = 0; + const guint8 *ie = NULL; + if (ies) { + ie = g_bytes_get_data(ies, &len); + } + if (len >= 2) { + const guint8 *end = ie + len; + while (ie + 1 < end && (ie + 1 + ie[1]) < end) { + static const guint8 WPS_OUI[] = {WMM_WPA1_WPS_OUI}; + if (ie[0] == WMM_WPA1_WPS_INFO && ie[1] >= sizeof(WPS_OUI) && + !memcmp(ie + 2, WPS_OUI, sizeof(WPS_OUI))) { + GSupplicantWPSInfo wps; + GVERBOSE_("found WPS_OUI (%u bytes)", ie[1]); + /* Version and state fields are mandatory */ + if (gsupplicant_bss_parse_wps_oui(ie + 6, ie[1] - 4, &wps) && + (wps.flags & WPS_INFO_REQUIRED) == WPS_INFO_REQUIRED && + wps.version == WPS_VERSION) { + wps_caps |= GSUPPLICANT_WPS_SUPPORTED; + if (wps.state == WPS_STATE_CONFIGURED) { + wps_caps |= GSUPPLICANT_WPS_CONFIGURED; + } + if (wps.registrar) { + wps_caps |= GSUPPLICANT_WPS_REGISTRAR; + } + if (wps.flags & WPS_INFO_METHODS) { + if (wps.methods & WPS_METHODS_PIN) { + wps_caps |= GSUPPLICANT_WPS_PIN; + GVERBOSE_("WPS method: pin"); + } + if (wps.methods & WPS_METHODS_BUTTON) { + wps_caps |= GSUPPLICANT_WPS_PUSH_BUTTON; + GVERBOSE_("WPS method: button"); + } + } else { + /* Assuming push and pin */ + GVERBOSE_("WPS methods: assuming pin+push"); + wps_caps |= GSUPPLICANT_WPS_PIN | + GSUPPLICANT_WPS_PUSH_BUTTON; + } + } + } + ie += ie[1] + 2; + } + } + return wps_caps; +} + +static +void +gsupplicant_bss_update_ies( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + GBytes* ies = gsupplicant_bss_get_bytes(self, PROXY_PROPERTY_NAME_IES); + if (!gsupplicant_bss_bytes_equal(self->ies, ies)) { + const GSUPPLICANT_WPS_CAPS wps_caps = gsupplicant_bss_parse_ies(ies); + if (self->ies) { + g_bytes_unref(self->ies); + } + self->ies = ies; + GVERBOSE("[%s] " PROXY_PROPERTY_NAME_IES ": %s", + self->path, gsupplicant_format_bytes(ies, FALSE)); + priv->pending_signals |= SIGNAL_BIT(IES); + if (self->wps_caps != wps_caps) { + self->wps_caps = wps_caps; + GDEBUG("[%s] WPS caps 0x%02x", self->path, wps_caps); + priv->pending_signals |= SIGNAL_BIT(WPS_CAPS); + } + } else if (ies) { + g_bytes_unref(ies); + } +} + +static +void +gsupplicant_bss_update_privacy( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + gboolean privacy = fi_w1_wpa_supplicant1_bss_get_privacy(priv->proxy); + if (self->privacy != privacy) { + self->privacy = privacy; + GVERBOSE("[%s] %s: %s", self->path, PROXY_PROPERTY_NAME_PRIVACY, + privacy ? "yes" : "no"); + priv->pending_signals |= SIGNAL_BIT(PRIVACY); + } +} + +static +void +gsupplicant_bss_update_mode( + GSupplicantBSS* self) +{ + static const GSupNameIntPair mode_map [] = { + { "infrastructure", GSUPPLICANT_BSS_MODE_INFRA }, + { "ad-hoc", GSUPPLICANT_BSS_MODE_AD_HOC }, + }; + GSupplicantBSSPriv* priv = self->priv; + GSUPPLICANT_BSS_MODE mode = GSUPPLICANT_BSS_MODE_UNKNOWN; + const char* name = fi_w1_wpa_supplicant1_bss_get_mode(priv->proxy); + const GSupNameIntPair* pair = gsupplicant_name_int_find_name_i(name, + mode_map, G_N_ELEMENTS(mode_map)); + if (pair) { + mode = pair->value; + } + if (self->mode != mode) { + self->mode = mode; + GVERBOSE("[%s] %s: %s", self->path, PROXY_PROPERTY_NAME_MODE, name); + priv->pending_signals |= SIGNAL_BIT(MODE); + } +} + +static +void +gsupplicant_bss_update_signal( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + const gint sig = fi_w1_wpa_supplicant1_bss_get_signal(priv->proxy); + if (self->signal != sig) { + self->signal = sig; + GVERBOSE("[%s] %s: %d", self->path, PROXY_PROPERTY_NAME_SIGNAL, sig); + priv->pending_signals |= SIGNAL_BIT(SIGNAL); + } +} + +static +void +gsupplicant_bss_update_frequency( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + const guint f = fi_w1_wpa_supplicant1_bss_get_frequency(priv->proxy); + if (self->frequency != f) { + self->frequency = f; + GVERBOSE("[%s] %s: %u", self->path, PROXY_PROPERTY_NAME_FREQUENCY, f); + priv->pending_signals |= SIGNAL_BIT(FREQUENCY); + } +} + +static +void +gsupplicant_bss_clear_rates( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + if (self->rates) { + self->rates = NULL; + memset(&priv->rates, 0, sizeof(priv->rates)); + g_free(priv->rates_values); + priv->rates_values = NULL; + priv->pending_signals |= SIGNAL_BIT(RATES); + GVERBOSE("[%s] %s: ", self->path, PROXY_PROPERTY_NAME_RATES); + } + if (self->maxrate) { + self->maxrate = 0; + priv->pending_signals |= SIGNAL_BIT(MAXRATE); + } +} + +static +void +gsupplicant_bss_update_rates( + GSupplicantBSS* self) +{ + GSupplicantBSSPriv* priv = self->priv; + GVariant* value = fi_w1_wpa_supplicant1_bss_dup_rates(priv->proxy); + if (value) { + gsize n = 0; + const guint* values; + if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT)) { + GVariant* tmp = g_variant_get_variant(value); + g_variant_unref(value); + value = tmp; + } + values = g_variant_get_fixed_array(value, &n, sizeof(guint)); + if (values) { + if (priv->rates.count != n || + memcmp(priv->rates.values, values, sizeof(guint)*n)) { + guint i, maxrate = 0; + + /* Store the rates */ + g_free(priv->rates_values); + priv->rates_values = g_memdup(values, sizeof(guint)*n); + priv->rates.values = priv->rates_values; + priv->rates.count = n; + self->rates = &priv->rates; + priv->pending_signals |= SIGNAL_BIT(RATES); + + /* Update the maximum rate */ + for (i=0; imaxrate != maxrate) { + self->maxrate = maxrate; + priv->pending_signals |= SIGNAL_BIT(MAXRATE); + } + +#if GUTIL_LOG_VERBOSE + if (GLOG_ENABLED(GUTIL_LOG_VERBOSE)) { + GString* sb = g_string_new("["); + for (i=0; i 0) g_string_append_c(sb, ','); + g_string_append_printf(sb, "%u", values[i]); + } + g_string_append_c(sb, ']'); + GVERBOSE("[%s] %s: %s", self->path, + PROXY_PROPERTY_NAME_RATES, sb->str); + g_string_free(sb, TRUE); + } +#endif + } + } else { + gsupplicant_bss_clear_rates(self); + } + g_variant_unref(value); + } else { + gsupplicant_bss_clear_rates(self); + } +} + +static +void +gsupplicant_bss_proxy_gproperties_changed( + GDBusProxy* proxy, + GVariant* changed, + GStrv invalidated, + gpointer data) +{ + GSupplicantBSS* self = GSUPPLICANT_BSS(data); + GSupplicantBSSPriv* priv = self->priv; + if (invalidated) { + char** ptr; + for (ptr = invalidated; *ptr; ptr++) { + const char* name = *ptr; + if (!strcmp(name, PROXY_PROPERTY_NAME_SSID)) { + if (self->ssid) { + g_bytes_unref(self->ssid); + g_free(priv->ssid_str); + self->ssid = NULL; + self->ssid_str = priv->ssid_str = NULL; + priv->pending_signals |= SIGNAL_BIT(SSID); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_BSSID)) { + if (self->bssid) { + g_bytes_unref(self->bssid); + self->bssid = NULL; + priv->pending_signals |= SIGNAL_BIT(BSSID); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_WPA)) { + if (self->wpa) { + self->wpa = NULL; + priv->pending_signals |= SIGNAL_BIT(WPA); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_RSN)) { + if (self->rsn) { + self->rsn = NULL; + priv->pending_signals |= SIGNAL_BIT(RSN); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_IES)) { + if (self->ies) { + g_bytes_unref(self->ies); + self->ies = NULL; + priv->pending_signals |= SIGNAL_BIT(IES); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_PRIVACY)) { + if (self->privacy) { + self->privacy = FALSE; + priv->pending_signals |= SIGNAL_BIT(PRIVACY); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_MODE)) { + if (self->mode != GSUPPLICANT_BSS_MODE_UNKNOWN) { + self->mode = GSUPPLICANT_BSS_MODE_UNKNOWN; + priv->pending_signals |= SIGNAL_BIT(MODE); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_SIGNAL)) { + if (self->signal) { + self->signal = 0; + priv->pending_signals |= SIGNAL_BIT(SIGNAL); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_FREQUENCY)) { + if (self->frequency) { + self->frequency = 0; + priv->pending_signals |= SIGNAL_BIT(FREQUENCY); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_RATES)) { + gsupplicant_bss_clear_rates(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_FREQUENCY)) { + if (self->frequency) { + self->frequency = 0; + priv->pending_signals |= SIGNAL_BIT(FREQUENCY); + } + } + } + } + if (changed) { + GVariantIter it; + GVariant* value; + const char* name; + g_variant_iter_init(&it, changed); + while (g_variant_iter_next(&it, "{&sv}", &name, &value)) { + if (!strcmp(name, PROXY_PROPERTY_NAME_SSID)) { + gsupplicant_bss_update_ssid(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_BSSID)) { + gsupplicant_bss_update_bssid(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_WPA)) { + gsupplicant_bss_update_wpa(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_RSN)) { + gsupplicant_bss_update_rsn(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_IES)) { + gsupplicant_bss_update_ies(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_PRIVACY)) { + gsupplicant_bss_update_privacy(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_MODE)) { + gsupplicant_bss_update_mode(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_SIGNAL)) { + gsupplicant_bss_update_signal(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_FREQUENCY)) { + gsupplicant_bss_update_frequency(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_RATES)) { + gsupplicant_bss_update_rates(self); + } + g_variant_unref(value); + } + } + gsupplicant_bss_emit_pending_signals(self); +} + +static +void +gsupplicant_bss_proxy_properties_changed( + GDBusProxy* proxy, + GVariant* change, + gpointer data) +{ + gsupplicant_bss_proxy_gproperties_changed(proxy, change, NULL, data); +} + +static +void +gsupplicant_bss_interface_valid_changed( + GSupplicantInterface* iface, + void* data) +{ + GSupplicantBSS* self = GSUPPLICANT_BSS(data); + GASSERT(self->iface == iface); + gsupplicant_bss_update_valid(self); + gsupplicant_bss_update_present(self); + gsupplicant_bss_emit_pending_signals(self); +} + +static +void +gsupplicant_bss_interface_bsss_changed( + GSupplicantInterface* iface, + void* data) +{ + GSupplicantBSS* self = GSUPPLICANT_BSS(data); + GASSERT(self->iface == iface); + gsupplicant_bss_update_present(self); + gsupplicant_bss_emit_pending_signals(self); +} + +static +void +gsupplicant_bss_proxy_created( + GObject* bus, + GAsyncResult* result, + gpointer data) +{ + GSupplicantBSS* self = GSUPPLICANT_BSS(data); + GSupplicantBSSPriv* priv = self->priv; + GError* error = NULL; + GASSERT(!self->valid); + GASSERT(!priv->proxy); + priv->proxy = fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_finish(result, + &error); + if (priv->proxy) { + priv->proxy_handler_id[PROXY_GPROPERTIES_CHANGED] = + g_signal_connect(priv->proxy, "g-properties-changed", + G_CALLBACK(gsupplicant_bss_proxy_gproperties_changed), self); + priv->proxy_handler_id[PROXY_PROPERTIES_CHANGED] = + g_signal_connect(priv->proxy, "properties-changed", + G_CALLBACK(gsupplicant_bss_proxy_properties_changed), self); + + priv->iface_handler_id[INTERFACE_VALID_CHANGED] = + gsupplicant_interface_add_handler(self->iface, + GSUPPLICANT_INTERFACE_PROPERTY_VALID, + gsupplicant_bss_interface_valid_changed, self); + priv->iface_handler_id[INTERFACE_BSSS_CHANGED] = + gsupplicant_interface_add_handler(self->iface, + GSUPPLICANT_INTERFACE_PROPERTY_BSSS, + gsupplicant_bss_interface_bsss_changed, self); + + gsupplicant_bss_update_valid(self); + gsupplicant_bss_update_present(self); + gsupplicant_bss_update_ssid(self); + gsupplicant_bss_update_bssid(self); + gsupplicant_bss_update_wpa(self); + gsupplicant_bss_update_rsn(self); + gsupplicant_bss_update_ies(self); + gsupplicant_bss_update_privacy(self); + gsupplicant_bss_update_mode(self); + gsupplicant_bss_update_frequency(self); + gsupplicant_bss_update_rates(self); + gsupplicant_bss_update_signal(self); + + gsupplicant_bss_emit_pending_signals(self); + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + gsupplicant_bss_unref(self); +} + +static +void +gsupplicant_bss_destroyed( + gpointer key, + GObject* dead) +{ + GVERBOSE_("%s", (char*)key); + GASSERT(gsupplicant_bss_table); + if (gsupplicant_bss_table) { + GASSERT(g_hash_table_lookup(gsupplicant_bss_table, key) == dead); + g_hash_table_remove(gsupplicant_bss_table, key); + if (g_hash_table_size(gsupplicant_bss_table) == 0) { + g_hash_table_unref(gsupplicant_bss_table); + gsupplicant_bss_table = NULL; + } + } +} + +static +GSupplicantBSS* +gsupplicant_bss_create( + const char* path) +{ + /* + * Let's assume that BSS path has the following format: + * + * /fi/w1/wpa_supplicant1/Interfaces/xxx/BSSs/yyy + * + * and we just have to strip the last two elements of the path to get + * the interface path. + */ + int slash_count = 0; + const char* ptr; + for (ptr = path + strlen(path); ptr > path; ptr--) { + if (ptr[0] == '/') { + slash_count++; + if (slash_count == 2) { + break; + } + } + } + if (ptr > path) { + GSupplicantInterface* iface; + char* path2 = g_strdup(path); + /* Temporarily shorten the path to lookup the interface */ + const gsize slash_index = ptr - path; + path2[slash_index] = 0; + GDEBUG_("%s -> %s", path, path2); + iface = gsupplicant_interface_new(path2); + if (iface) { + GSupplicantBSS* self = g_object_new(GSUPPLICANT_BSS_TYPE,NULL); + GSupplicantBSSPriv* priv = self->priv; + /* Path is already allocated (but truncated) */ + path2[slash_index] = '/'; + self->path = priv->path = path2; + self->iface = iface; + fi_w1_wpa_supplicant1_bss_proxy_new_for_bus(GSUPPLICANT_BUS_TYPE, + G_DBUS_PROXY_FLAGS_NONE, GSUPPLICANT_SERVICE, self->path, NULL, + gsupplicant_bss_proxy_created, gsupplicant_bss_ref(self)); + return self; + } + g_free(path2); + } + return NULL; +} + +/*==========================================================================* + * API + *==========================================================================*/ + +GSupplicantBSS* +gsupplicant_bss_new( + const char* path) +{ + GSupplicantBSS* self = NULL; + if (G_LIKELY(path)) { + self = gsupplicant_bss_table ? + gsupplicant_bss_ref(g_hash_table_lookup(gsupplicant_bss_table, + path)) : NULL; + if (!self) { + self = gsupplicant_bss_create(path); + if (self) { + gpointer key = g_strdup(path); + if (!gsupplicant_bss_table) { + gsupplicant_bss_table = + g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + } + g_hash_table_replace(gsupplicant_bss_table, key, self); + g_object_weak_ref(G_OBJECT(self), gsupplicant_bss_destroyed, + key); + } + } + } + return self; +} + +GSupplicantBSS* +gsupplicant_bss_ref( + GSupplicantBSS* self) +{ + if (G_LIKELY(self)) { + g_object_ref(GSUPPLICANT_BSS(self)); + return self; + } else { + return NULL; + } +} + +void +gsupplicant_bss_unref( + GSupplicantBSS* self) +{ + if (G_LIKELY(self)) { + g_object_unref(GSUPPLICANT_BSS(self)); + } +} + +gulong +gsupplicant_bss_add_property_changed_handler( + GSupplicantBSS* self, + GSUPPLICANT_BSS_PROPERTY property, + GSupplicantBSSPropertyFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signal_name; + char buf[sizeof(SIGNAL_PROPERTY_CHANGED_NAME) + 2 + + SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN]; + if (property) { + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_NAME "::" + SIGNAL_PROPERTY_CHANGED_DETAIL, property); + buf[sizeof(buf)-1] = 0; + signal_name = buf; + } else { + signal_name = SIGNAL_PROPERTY_CHANGED_NAME; + } + return g_signal_connect(self, signal_name, G_CALLBACK(fn), data); + } + return 0; +} + +gulong +gsupplicant_bss_add_handler( + GSupplicantBSS* self, + GSUPPLICANT_BSS_PROPERTY prop, + GSupplicantBSSFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signame; + switch (prop) { +#define SIGNAL_NAME_(P,p) case GSUPPLICANT_BSS_PROPERTY_##P: \ + signame = gsupplicant_bss_signame[SIGNAL_##P##_CHANGED]; \ + break; + GSUPPLICANT_BSS_PROPERTIES_(SIGNAL_NAME_) + default: + signame = NULL; + break; + } + if (G_LIKELY(signame)) { + return g_signal_connect(self, signame, G_CALLBACK(fn), data); + } + } + return 0; +} + +void +gsupplicant_bss_remove_handler( + GSupplicantBSS* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +gsupplicant_bss_remove_handlers( + GSupplicantBSS* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +GSUPPLICANT_SECURITY +gsupplicant_bss_security( + GSupplicantBSS* self) +{ + if (G_LIKELY(self) && self->valid && self->present) { + GSUPPLICANT_KEYMGMT keymgmt = gsupplicant_bss_keymgmt(self); + if (keymgmt & (GSUPPLICANT_KEYMGMT_WPA_EAP | + GSUPPLICANT_KEYMGMT_WPA_FT_EAP | + GSUPPLICANT_KEYMGMT_WPA_EAP_SHA256 | + GSUPPLICANT_KEYMGMT_IEEE8021X)) { + return GSUPPLICANT_SECURITY_EAP; + } + if (keymgmt & (GSUPPLICANT_KEYMGMT_WPA_PSK | + GSUPPLICANT_KEYMGMT_WPA_FT_PSK | + GSUPPLICANT_KEYMGMT_WPA_PSK_SHA256)) { + return GSUPPLICANT_SECURITY_PSK; + } + if (self->privacy) { + return GSUPPLICANT_SECURITY_WEP; + } + } + return GSUPPLICANT_SECURITY_NONE; +} + +GSUPPLICANT_KEYMGMT +gsupplicant_bss_keymgmt( + GSupplicantBSS* self) +{ + GSUPPLICANT_KEYMGMT keymgmt = GSUPPLICANT_KEYMGMT_INVALID; + if (G_LIKELY(self)) { + if (self->wpa) { + keymgmt |= self->wpa->keymgmt; + } + if (self->rsn) { + keymgmt |= self->rsn->keymgmt; + } + } + return keymgmt; +} + +GSUPPLICANT_CIPHER +gsupplicant_bss_pairwise( + GSupplicantBSS* self) +{ + GSUPPLICANT_CIPHER pairwise = GSUPPLICANT_CIPHER_INVALID; + if (G_LIKELY(self)) { + if (self->wpa) { + pairwise |= self->wpa->pairwise; + } + if (self->rsn) { + pairwise |= self->rsn->pairwise; + } + } + return pairwise; +} + +GCancellable* +gsupplicant_bss_connect( + GSupplicantBSS* self, + const GSupplicantBSSConnectParams* cp, + guint flags, /* None defined yet */ + GSupplicantBSSStringResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfaceStringResultFunc call_done = NULL; + GDestroyNotify call_free = NULL; + void* call_data = NULL; + GCancellable* cancel; + GSupplicantNetworkParams np; + gsupplicant_bss_fill_network_params(self, cp, flags, &np); + if (fn) { + call_data = gsupplicant_bss_connect_data_new(self, fn, data); + call_done = gsupplicant_bss_connect_done; + call_free = gsupplicant_bss_connect_free; + } + cancel = gsupplicant_interface_add_network_full(self->iface, NULL, + &np, GSUPPLICANT_ADD_NETWORK_DELETE_OTHER | + GSUPPLICANT_ADD_NETWORK_SELECT | GSUPPLICANT_ADD_NETWORK_ENABLE, + call_done, call_free, call_data); + if (cancel) { + return cancel; + } else if (call_free) { + call_free(call_data); + } + } + return NULL; +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +/** + * Per instance initializer + */ +static +void +gsupplicant_bss_init( + GSupplicantBSS* self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, GSUPPLICANT_BSS_TYPE, + GSupplicantBSSPriv); +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +gsupplicant_bss_dispose( + GObject* object) +{ + GSupplicantBSS* self = GSUPPLICANT_BSS(object); + GSupplicantBSSPriv* priv = self->priv; + if (priv->proxy) { + gutil_disconnect_handlers(priv->proxy, priv->proxy_handler_id, + G_N_ELEMENTS(priv->proxy_handler_id)); + g_object_unref(priv->proxy); + priv->proxy = NULL; + } + gsupplicant_interface_remove_handlers(self->iface, priv->iface_handler_id, + G_N_ELEMENTS(priv->iface_handler_id)); + G_OBJECT_CLASS(SUPER_CLASS)->dispose(object); +} + +/** + * Final stage of deinitialization + */ +static +void +gsupplicant_bss_finalize( + GObject* object) +{ + GSupplicantBSS* self = GSUPPLICANT_BSS(object); + GSupplicantBSSPriv* priv = self->priv; + GASSERT(!priv->proxy); + if (self->ssid) { + g_bytes_unref(self->ssid); + } + if (self->bssid) { + g_bytes_unref(self->bssid); + } + if (self->ies) { + g_bytes_unref(self->ies); + } + g_free(priv->ssid_str); + g_free(priv->rates_values); + g_free(priv->path); + gsupplicant_interface_unref(self->iface); + G_OBJECT_CLASS(SUPER_CLASS)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +gsupplicant_bss_class_init( + GSupplicantBSSClass* klass) +{ + int i; + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = gsupplicant_bss_dispose; + object_class->finalize = gsupplicant_bss_finalize; + g_type_class_add_private(klass, sizeof(GSupplicantBSSPriv)); + for (i=0; i + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_DBUS_H +#define GSUPPLICANT_DBUS_H + +#include "gsupplicant_types.h" + +#define GSUPPLICANT_BUS_TYPE G_BUS_TYPE_SYSTEM +#define GSUPPLICANT_SERVICE "fi.w1.wpa_supplicant1" +#define GSUPPLICANT_PATH "/fi/w1/wpa_supplicant1" + +#endif /* GSUPPLICANT_DBUS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/gsupplicant_error.c b/src/gsupplicant_error.c new file mode 100644 index 0000000..3dcca9b --- /dev/null +++ b/src/gsupplicant_error.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant_error.h" +#include "gsupplicant_dbus.h" + +#define GSUPPLICANT_ERROR_ENTRY(E,e) \ + {GSUPPLICANT_ERROR_##E, GSUPPLICANT_SERVICE "." e}, + +static const GDBusErrorEntry gsupplicant_errors[] = { + GSUPPLICANT_ERRORS(GSUPPLICANT_ERROR_ENTRY) +}; + +GQuark +gsupplicant_error_quark() +{ + static volatile gsize gsupplicant_error_quark_value = 0; + g_dbus_error_register_error_domain("gsupplicant-error-quark", + &gsupplicant_error_quark_value, gsupplicant_errors, + G_N_ELEMENTS(gsupplicant_errors)); + return (GQuark)gsupplicant_error_quark_value; +} + +gboolean +gsupplicant_is_error( + const GError* error, + GSUPPLICANT_ERROR_CODE code) +{ + return error && error->domain == GSUPPLICANT_ERROR && error->code == code; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/gsupplicant_interface.c b/src/gsupplicant_interface.c new file mode 100644 index 0000000..e2f35ee --- /dev/null +++ b/src/gsupplicant_interface.c @@ -0,0 +1,2877 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant_interface.h" +#include "gsupplicant_network.h" +#include "gsupplicant_bss.h" +#include "gsupplicant.h" +#include "gsupplicant_util_p.h" +#include "gsupplicant_dbus.h" +#include "gsupplicant_log.h" + +#include +#include + +/* Generated headers */ +#include "fi.w1.wpa_supplicant1.Interface.h" +#include "fi.w1.wpa_supplicant1.Interface.WPS.h" + +/* Constants */ +#define WPS_DEFAULT_CONNECT_TIMEOUT_SEC (30) + +/* Internal data structures */ +typedef union gsupplicant_interface_call_func_union { + GCallback cb; + GSupplicantInterfaceResultFunc fn_void; + GSupplicantInterfaceStringResultFunc fn_string; + GSupplicantInterfaceSignalPollResultFunc fn_signal_poll; +} GSupplicantInterfaceCallFuncUnion; + +typedef struct gsupplicant_interface_call GSupplicantInterfaceCall; + +typedef +void +(*GSupplicantInterfaceCallFinishFunc)( + GSupplicantInterfaceCall* call, + GAsyncResult* result); + +struct gsupplicant_interface_call { + GSupplicantInterface* iface; + GCancellable* cancel; + GSupplicantInterfaceCallFinishFunc finish; + GSupplicantInterfaceCallFuncUnion fn; + GDestroyNotify destroy; + void* data; +}; + +enum add_network_handler_id { + ADD_NETWORK_VALID_CHANGED, + ADD_NETWORK_ENABLED_CHANGED, + ADD_NETWORK_HANDLER_COUNT +}; + +typedef struct gsupplicant_interface_add_network_call { + GSupplicantInterface* iface; + GSupplicantNetwork* network; + gulong network_event_id[ADD_NETWORK_HANDLER_COUNT]; + GCancellable* cancel; + gulong cancel_id; + GVariant* args; + gboolean pending; + GSupplicantInterfaceStringResultFunc fn; + GDestroyNotify destroy; + void* data; + guint flags; + char* path; +} GSupplicantInterfaceAddNetworkCall; + +enum gsupplicant_wps_proxy_handler_id { + WPS_PROXY_EVENT, + WPS_PROXY_CREDENTIALS, + WPS_PROXY_HANDLER_COUNT +}; + +typedef enum gsupplicant_wps_connect_status { + WPS_CONNECT_PENDING, + WPS_CONNECT_SUCCESS, + WPS_CONNECT_FAIL, + WPS_CONNECT_M2D, + WPS_CONNECT_PBC_OVERLAP +} WPS_CONNECT_STATE; + +typedef struct gsupplicant_interface_wps_connect { + GSupplicantInterface* iface; + GSupplicantWPSParams wps; + FiW1Wpa_supplicant1InterfaceWPS* wps_proxy; + gulong wps_proxy_handler_id[WPS_PROXY_HANDLER_COUNT]; + char* pin; + char* new_pin; + GCancellable* cancel; + gulong cancel_id; + guint timeout_id; + WPS_CONNECT_STATE state; + GSupplicantInterfaceStringResultFunc fn; + GDestroyNotify destroy; + void* data; +} GSupplicantInterfaceWPSConnect; + +/* Object definition */ +enum supplicant_interface_proxy_handler_id { + PROXY_GPROPERTIES_CHANGED, + PROXY_PROPERTIES_CHANGED, + PROXY_BSS_ADDED, + PROXY_BSS_REMOVED, + PROXY_NETWORK_ADDED, + PROXY_NETWORK_REMOVED, + PROXY_NETWORK_SELECTED, + PROXY_HANDLER_COUNT +}; + +enum supplicant_handler_id { + SUPPLICANT_VALID_CHANGED, + SUPPLICANT_INTERFACES_CHANGED, + SUPPLICANT_HANDLER_COUNT +}; + +struct gsupplicant_interface_priv { + GDBusConnection* bus; + FiW1Wpa_supplicant1Interface* proxy; + gulong proxy_handler_id[PROXY_HANDLER_COUNT]; + gulong supplicant_handler_id[SUPPLICANT_HANDLER_COUNT]; + GSupplicantWPSCredentials wps_credentials; + guint32 pending_signals; + GStrV* bsss; + GStrV* networks; + char* path; + char* country; + char* driver; + char* ifname; + char* bridge_ifname; + char* current_bss; + char* current_network; +}; + +typedef GObjectClass GSupplicantInterfaceClass; +G_DEFINE_TYPE(GSupplicantInterface, gsupplicant_interface, G_TYPE_OBJECT) +#define GSUPPLICANT_INTERFACE_TYPE (gsupplicant_interface_get_type()) +#define GSUPPLICANT_INTERFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GSUPPLICANT_INTERFACE_TYPE, GSupplicantInterface)) +#define SUPER_CLASS gsupplicant_interface_parent_class + +/* Supplicant interface properties */ +#define GSUPPLICANT_INTERFACE_PROPERTIES_(p) \ + p(VALID,valid) \ + p(PRESENT,present) \ + p(CAPS,caps) \ + p(STATE,state) \ + p(WPS_CREDENTIALS,wps-credentials) \ + p(SCANNING,scanning) \ + p(AP_SCAN,ap-scan) \ + p(COUNTRY,country) \ + p(DRIVER,driver) \ + p(IFNAME,ifname) \ + p(BRIDGE_IFNAME,bridge-ifname) \ + p(CURRENT_BSS,current-bss) \ + p(CURRENT_NETWORK,current-network) \ + p(BSSS,bsss) \ + p(NETWORKS,networks) \ + p(SCAN_INTERVAL,scan-interval) + +typedef enum gsupplicant_interface_signal { +#define SIGNAL_ENUM_(P,p) SIGNAL_##P##_CHANGED, + GSUPPLICANT_INTERFACE_PROPERTIES_(SIGNAL_ENUM_) +#undef SIGNAL_ENUM_ + SIGNAL_PROPERTY_CHANGED, + SIGNAL_COUNT +} GSUPPLICANT_INTERFACE_SIGNAL; + +#define SIGNAL_BIT(name) (1 << SIGNAL_##name##_CHANGED) + +/* + * The code assumes that VALID is the first one and that their number + * doesn't exceed the number of bits in pending_signals (currently, 32) + */ +G_STATIC_ASSERT(SIGNAL_VALID_CHANGED == 0); +G_STATIC_ASSERT(SIGNAL_PROPERTY_CHANGED <= 32); + +/* Assert that we have covered all publicly defined properties */ +G_STATIC_ASSERT((int)SIGNAL_PROPERTY_CHANGED == + ((int)GSUPPLICANT_INTERFACE_PROPERTY_COUNT-1)); + +#define SIGNAL_PROPERTY_CHANGED_NAME "property-changed" +#define SIGNAL_PROPERTY_CHANGED_DETAIL "%x" +#define SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN (8) + +static GQuark gsupplicant_interface_property_quarks[SIGNAL_PROPERTY_CHANGED]; +static guint gsupplicant_interface_signals[SIGNAL_COUNT]; +static const char* gsupplicant_interface_signame[] = { +#define SIGNAL_NAME_(P,p) #p "-changed", + GSUPPLICANT_INTERFACE_PROPERTIES_(SIGNAL_NAME_) +#undef SIGNAL_NAME_ + SIGNAL_PROPERTY_CHANGED_NAME +}; + +G_STATIC_ASSERT(G_N_ELEMENTS(gsupplicant_interface_signame) == SIGNAL_COUNT); + +/* Proxy properties */ +#define PROXY_PROPERTY_NAME_CAPABILITIES "Capabilities" +#define PROXY_PROPERTY_NAME_STATE "State" +#define PROXY_PROPERTY_NAME_SCANNING "Scanning" +#define PROXY_PROPERTY_NAME_AP_SCAN "ApScan" +#define PROXY_PROPERTY_NAME_BSS_EXPIRE_AGE "BSSExpireAge" +#define PROXY_PROPERTY_NAME_BSS_EXPIRE_COUNT "BSSExpireCount" +#define PROXY_PROPERTY_NAME_COUNTRY "Country" +#define PROXY_PROPERTY_NAME_DRIVER "Driver" +#define PROXY_PROPERTY_NAME_IFNAME "Ifname" +#define PROXY_PROPERTY_NAME_BRIDGE_IFNAME "BridgeIfname" +#define PROXY_PROPERTY_NAME_CURRENT_BSS "CurrentBSS" +#define PROXY_PROPERTY_NAME_CURRENT_NETWORK "CurrentNetwork" +#define PROXY_PROPERTY_NAME_CURRENT_AUTH_MODE "CurrentAuthMode" +#define PROXY_PROPERTY_NAME_BLOBS "Blobs" +#define PROXY_PROPERTY_NAME_BSSS "BSSs" +#define PROXY_PROPERTY_NAME_NETWORKS "Networks" +#define PROXY_PROPERTY_NAME_FAST_REAUTH "FastReauth" +#define PROXY_PROPERTY_NAME_SCAN_INTERVAL "ScanInterval" +#define PROXY_PROPERTY_NAME_PKCS11_ENGINE_PATH "PKCS11EnginePath" +#define PROXY_PROPERTY_NAME_PKCS11_MODULE_PATH "PKCS11ModulePath" +#define PROXY_PROPERTY_NAME_DISCONNECT_REASON "DisconnectReason" + +/* Weak references to the instances of GSupplicantInterface */ +static GHashTable* gsupplicant_interface_table = NULL; + +/* States */ +static const GSupNameIntPair gsupplicant_interface_states [] = { + { "disconnected", GSUPPLICANT_INTERFACE_STATE_DISCONNECTED }, + { "inactive", GSUPPLICANT_INTERFACE_STATE_INACTIVE }, + { "scanning", GSUPPLICANT_INTERFACE_STATE_SCANNING }, + { "authenticating", GSUPPLICANT_INTERFACE_STATE_AUTHENTICATING }, + { "associating", GSUPPLICANT_INTERFACE_STATE_ASSOCIATING }, + { "associated", GSUPPLICANT_INTERFACE_STATE_ASSOCIATED }, + { "4way_handshake", GSUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE }, + { "group_handshake", GSUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE }, + { "completed", GSUPPLICANT_INTERFACE_STATE_COMPLETED }, + { "unknown", GSUPPLICANT_INTERFACE_STATE_UNKNOWN } +}; + +/*==========================================================================* + * Property change signals + *==========================================================================*/ + +static inline +GSUPPLICANT_INTERFACE_PROPERTY +gsupplicant_interface_property_from_signal( + GSUPPLICANT_INTERFACE_SIGNAL sig) +{ + switch (sig) { +#define SIGNAL_PROPERTY_MAP_(P,p) \ + case SIGNAL_##P##_CHANGED: return GSUPPLICANT_INTERFACE_PROPERTY_##P; + GSUPPLICANT_INTERFACE_PROPERTIES_(SIGNAL_PROPERTY_MAP_) +#undef SIGNAL_PROPERTY_MAP_ + default: /* unreachable */ return GSUPPLICANT_INTERFACE_PROPERTY_ANY; + } +} + +/* "/" means that there's no association at all. */ +static inline +const char* +gsupplicant_interface_association_path_filter( + const char* path) +{ + return (path && path[0] == '/' && path[1] == '\0') ? NULL : path; +} + +static +void +gsupplicant_interface_signal_property_change( + GSupplicantInterface* self, + GSUPPLICANT_INTERFACE_SIGNAL sig, + GSUPPLICANT_INTERFACE_PROPERTY prop) +{ + GSupplicantInterfacePriv* priv = self->priv; + GASSERT(prop > GSUPPLICANT_INTERFACE_PROPERTY_ANY); + GASSERT(prop < GSUPPLICANT_INTERFACE_PROPERTY_COUNT); + GASSERT(sig < G_N_ELEMENTS(gsupplicant_interface_property_quarks)); + if (!gsupplicant_interface_property_quarks[sig]) { + char buf[SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN + 1]; + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_DETAIL, prop); + buf[sizeof(buf)-1] = 0; + gsupplicant_interface_property_quarks[sig] = g_quark_from_string(buf); + } + priv->pending_signals &= ~(1 << sig); + g_signal_emit(self, gsupplicant_interface_signals[sig], 0); + g_signal_emit(self, gsupplicant_interface_signals[SIGNAL_PROPERTY_CHANGED], + gsupplicant_interface_property_quarks[sig], prop); +} + +static +void +gsupplicant_interface_emit_pending_signals( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + GSUPPLICANT_INTERFACE_SIGNAL sig; + gboolean valid_changed; + + /* Handlers could drops their references to us */ + gsupplicant_interface_ref(self); + + /* VALID is the last one to be emitted if we BECOME valid */ + if ((priv->pending_signals & SIGNAL_BIT(VALID)) && self->valid) { + priv->pending_signals &= ~SIGNAL_BIT(VALID); + valid_changed = TRUE; + } else { + valid_changed = FALSE; + } + + /* Emit the signals. Not that in case if valid has become FALSE, then + * VALID is emitted first, otherwise it's emitted last */ + for (sig = SIGNAL_VALID_CHANGED; + sig < SIGNAL_PROPERTY_CHANGED && priv->pending_signals; + sig++) { + if (priv->pending_signals & (1 << sig)) { + gsupplicant_interface_signal_property_change(self, sig, + gsupplicant_interface_property_from_signal(sig)); + } + } + + /* Then emit VALID if valid has become TRUE */ + if (valid_changed) { + gsupplicant_interface_signal_property_change(self, + SIGNAL_VALID_CHANGED, GSUPPLICANT_INTERFACE_PROPERTY_VALID); + } + + /* And release the temporary reference */ + gsupplicant_interface_unref(self); +} + +/*==========================================================================* + * Network parameters + *==========================================================================*/ + +static +void +gsupplicant_interface_add_network_args_security_wep( + GVariantBuilder* builder, + const GSupplicantNetworkParams* np) +{ + if (np->passphrase && np->passphrase[0]) { + const char* key = "wep_key0"; + const gsize len = strlen(np->passphrase); + const guint8* bin = NULL; + guint8 buf[13]; + + if (len == 10 || len == 26) { + /* Check for hex representation of the WEP key */ + bin = gsupplicant_hex2bin(np->passphrase, len, buf); + } + + if (bin) { + gsupplicant_dict_add_value(builder, key, g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, bin, len/2, 1)); + } else { + gsupplicant_dict_add_string(builder, key, np->passphrase); + } + gsupplicant_dict_add_uint32(builder, "wep_tx_keyidx", 0); + } +} + +static +void +gsupplicant_interface_add_network_args_security_psk( + GVariantBuilder* builder, + const GSupplicantNetworkParams* np) +{ + if (np->passphrase && np->passphrase[0]) { + const char* key = "psk"; + const gsize len = strlen(np->passphrase); + const guint8* bin = NULL; + guint8 buf[32]; + + if (len == 64) { + /* Check for hex representation of the 256-bit pre-shared key */ + bin = gsupplicant_hex2bin(np->passphrase, len, buf); + } + + if (bin) { + gsupplicant_dict_add_value(builder, key, g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, bin, len/2, 1)); + } else { + gsupplicant_dict_add_string(builder, key, np->passphrase); + } + } +} + +static +void +gsupplicant_interface_add_network_args_security_peap( + GVariantBuilder* builder, + const GSupplicantNetworkParams* np) +{ + /* + * Multiple protocols in phase2 should be allowed, + * e.g "autheap=MSCHAPV2 autheap=MD5" for EAP-TTLS + * Does the order matter? + */ + if (np->phase2 != GSUPPLICANT_EAP_METHOD_NONE) { + const char* ca_cert2 = + gsupplicant_check_abs_path(np->ca_cert_file2); + const char* client_cert2 = + gsupplicant_check_abs_path(np->client_cert_file2); + const char* auth = (np->auth_flags & GSUPPLICANT_AUTH_PHASE2_AUTHEAP) ? + "autheap" : "auth"; + GString* buf = g_string_new(NULL); + guint found, phase2 = np->phase2; + const char* method = gsupplicant_eap_method_name(np->phase2, &found); + while (method) { + if (buf->len) g_string_append_c(buf, ' '); + g_string_append(buf, auth); + g_string_append_c(buf, '='); + g_string_append(buf, method); + phase2 &= ~found; + } + if (buf->len > 0) { + gsupplicant_dict_add_string(builder, "phase2", buf->str); + } + g_string_free(buf, TRUE); + + gsupplicant_dict_add_string0(builder, "ca_cert2", ca_cert2); + if (client_cert2) { + if (np->private_key_file2 && np->private_key_file2[0]) { + const char* private_key2 = + gsupplicant_check_abs_path(np->private_key_file2); + if (private_key2) { + gsupplicant_dict_add_string(builder, "client_cert2", + client_cert2); + gsupplicant_dict_add_string(builder, "private_key2", + np->private_key_file2); + gsupplicant_dict_add_string_ne(builder, + "private_key_passwd2", np->private_key_passphrase2); + } + } else { + GWARN("Missing private key for phase2"); + } + } + gsupplicant_dict_add_string_ne(builder, "subject_match2", + np->subject_match2); + gsupplicant_dict_add_string_ne(builder, "altsubject_match2", + np->altsubject_match2); + gsupplicant_dict_add_string_ne(builder, "domain_suffix_match2", + np->domain_suffix_match2); + } +} + +static +void +gsupplicant_interface_add_network_args_security_eap( + GVariantBuilder* builder, + const GSupplicantNetworkParams* np) +{ + guint found; + const char* ca_cert = gsupplicant_check_abs_path(np->ca_cert_file); + const char* client_cert = gsupplicant_check_abs_path(np->client_cert_file); + const char* method = gsupplicant_eap_method_name(np->eap, &found); + GASSERT(found == np->eap); /* Only one method should be specified */ + gsupplicant_dict_add_string_ne(builder, "eap", method); + switch (np->eap) { + case GSUPPLICANT_EAP_METHOD_NONE: + GERR_("No EAP method specified!"); + return; + case GSUPPLICANT_EAP_METHOD_PEAP: + case GSUPPLICANT_EAP_METHOD_TTLS: + gsupplicant_interface_add_network_args_security_peap(builder, np); + break; + case GSUPPLICANT_EAP_METHOD_TLS: + break; + default: + GWARN_("Unsupported EAP method %s", method); + break; + } + gsupplicant_dict_add_string_ne(builder, "identity", np->identity); + gsupplicant_dict_add_string_ne(builder, "anonymous_identity", + np->anonymous_identity); + gsupplicant_dict_add_string_ne(builder, "password", np->passphrase); + gsupplicant_dict_add_string0(builder, "ca_cert", ca_cert); + if (client_cert) { + if (np->private_key_file && np->private_key_file[0]) { + const char* private_key = + gsupplicant_check_abs_path(np->private_key_file); + if (private_key) { + gsupplicant_dict_add_string(builder, "client_cert", + client_cert); + gsupplicant_dict_add_string(builder, "private_key", + private_key); + gsupplicant_dict_add_string_ne(builder, "private_key_passwd", + np->private_key_passphrase); + } + } else { + GWARN("Missing private key"); + } + } + gsupplicant_dict_add_string_ne(builder, "domain_match", + np->domain_match); + gsupplicant_dict_add_string_ne(builder, "subject_match", + np->subject_match); + gsupplicant_dict_add_string_ne(builder, "altsubject_match", + np->altsubject_match); + gsupplicant_dict_add_string_ne(builder, "domain_suffix_match", + np->domain_suffix_match); +} + +static +void +gsupplicant_interface_add_network_args_security_ciphers( + GVariantBuilder* builder, + const GSupplicantNetworkParams* np) +{ + static const GSupNameIntPair ciphers [] = { + { "CCMP", GSUPPLICANT_CIPHER_CCMP }, + { "TKIP", GSUPPLICANT_CIPHER_TKIP }, + { "WEP104", GSUPPLICANT_CIPHER_WEP104 }, + { "WEP40", GSUPPLICANT_CIPHER_WEP40 } + }; + char* pairwise = gsupplicant_name_int_concat(np->pairwise, ' ', + ciphers, G_N_ELEMENTS(ciphers)); + char* group = gsupplicant_name_int_concat(np->group, ' ', + ciphers, G_N_ELEMENTS(ciphers)); + if (pairwise) { + gsupplicant_dict_add_string(builder, "pairwise", pairwise); + g_free(pairwise); + } + if (group) { + gsupplicant_dict_add_string(builder, "group", group); + g_free(group); + } +} + +static +void +gsupplicant_interface_add_network_args_security_proto( + GVariantBuilder* builder, + const GSupplicantNetworkParams* np) +{ + static const GSupNameIntPair protos [] = { + { "RSN", GSUPPLICANT_PROTOCOL_RSN }, + { "WPA", GSUPPLICANT_PROTOCOL_WPA } + }; + char* proto = gsupplicant_name_int_concat(np->protocol, ' ', + protos, G_N_ELEMENTS(protos)); + if (proto) { + gsupplicant_dict_add_string(builder, "proto", proto); + g_free(proto); + } +} + +static +GVariant* +gsupplicant_interface_add_network_args_new( + const GSupplicantNetworkParams* np) +{ + const char* key_mgmt = NULL; + const char* auth_alg = NULL; + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + gsupplicant_dict_add_bytes0(&builder, "ssid", np->ssid); + if (np->frequency) { + gsupplicant_dict_add_uint32(&builder, "frequency", np->frequency); + } + gsupplicant_dict_add_string_ne(&builder, "bgscan", np->bgscan); + gsupplicant_dict_add_uint32(&builder, "scan_ssid", np->scan_ssid); + gsupplicant_dict_add_uint32(&builder, "mode", np->mode); + switch (np->security) { + case GSUPPLICANT_SECURITY_NONE: + GDEBUG_("no security"); + key_mgmt = "NONE"; + auth_alg = "OPEN"; + break; + case GSUPPLICANT_SECURITY_WEP: + GDEBUG_("WEP security"); + key_mgmt = "NONE"; + auth_alg = "OPEN SHARED"; + gsupplicant_interface_add_network_args_security_wep(&builder, np); + gsupplicant_interface_add_network_args_security_ciphers(&builder, np); + break; + case GSUPPLICANT_SECURITY_PSK: + GDEBUG_("PSK security"); + key_mgmt = "WPA-PSK"; + gsupplicant_interface_add_network_args_security_psk(&builder, np); + gsupplicant_interface_add_network_args_security_proto(&builder, np); + gsupplicant_interface_add_network_args_security_ciphers(&builder, np); + break; + case GSUPPLICANT_SECURITY_EAP: + GDEBUG_("EAP security"); + key_mgmt = "WPA-EAP"; + gsupplicant_interface_add_network_args_security_eap(&builder, np); + gsupplicant_interface_add_network_args_security_proto(&builder, np); + gsupplicant_interface_add_network_args_security_ciphers(&builder, np); + break; + } + gsupplicant_dict_add_string0(&builder, "auth_alg", auth_alg); + gsupplicant_dict_add_string0(&builder, "key_mgmt", key_mgmt); + return g_variant_ref_sink(g_variant_builder_end(&builder)); +} + +/*==========================================================================* + * WPS Connect + *==========================================================================*/ + +static +void +gsupplicant_interface_clear_wps_credentials( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + priv->wps_credentials.auth_types = GSUPPLICANT_AUTH_NONE; + priv->wps_credentials.encr_types = GSUPPLICANT_WPS_ENCR_NONE; + priv->wps_credentials.key_index = 0; + if (priv->wps_credentials.bssid) { + g_bytes_unref(priv->wps_credentials.bssid); + priv->wps_credentials.bssid = NULL; + } + if (priv->wps_credentials.ssid) { + g_bytes_unref(priv->wps_credentials.ssid); + priv->wps_credentials.ssid = NULL; + } + if (priv->wps_credentials.key) { + g_bytes_unref(priv->wps_credentials.key); + priv->wps_credentials.key = NULL; + } + if (self->wps_credentials) { + self->wps_credentials = NULL; + priv->pending_signals |= SIGNAL_BIT(WPS_CREDENTIALS); + } +} + +static +void +gsupplicant_interface_wps_parse_creds( + const char* name, + GVariant* value, + void* data) +{ + GSupplicantWPSCredentials* wps = data; + if (!g_strcmp0(name, "BSSID")) { + if (wps->bssid) g_bytes_unref(wps->bssid); + wps->bssid = g_variant_get_data_as_bytes(value); + GVERBOSE(" %s: %s", name, gsupplicant_format_bytes(wps->bssid, TRUE)); + } else if (!g_strcmp0(name, "SSID")) { + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + gsize length = 0; + const char* ssid = g_variant_get_string(value, &length); + if (ssid) { + if (wps->ssid) g_bytes_unref(wps->ssid); + wps->ssid = g_bytes_new(ssid, length); + GVERBOSE(" %s: \"%.*s\"", name, (int)length, ssid); + } + } + } else if (!g_strcmp0(name, "AuthType")) { + static const GSupNameIntPair auth_types_map [] = { + { "open", GSUPPLICANT_AUTH_OPEN }, + { "shared", GSUPPLICANT_AUTH_SHARED }, + { "wpa-psk", GSUPPLICANT_AUTH_WPA_PSK }, + { "wpa-eap", GSUPPLICANT_AUTH_WPA_EAP }, + { "wpa2-eap", GSUPPLICANT_AUTH_WPA2_EAP }, + { "wpa2-psk", GSUPPLICANT_AUTH_WPA2_PSK } + }; + wps->auth_types = gsupplicant_parse_bits_array(0, name, value, + auth_types_map, G_N_ELEMENTS(auth_types_map)); + } else if (!g_strcmp0(name, "EncrType")) { + static const GSupNameIntPair encr_types_map [] = { + { "none", GSUPPLICANT_WPS_ENCR_NONE }, + { "wep", GSUPPLICANT_WPS_ENCR_WEP }, + { "tkip", GSUPPLICANT_WPS_ENCR_TKIP }, + { "aes", GSUPPLICANT_WPS_ENCR_AES } + }; + wps->encr_types = gsupplicant_parse_bits_array(0, name, value, + encr_types_map, G_N_ELEMENTS(encr_types_map)); + } else if (!g_strcmp0(name, "Key")) { + if (wps->key) g_bytes_unref(wps->key); + wps->key = g_variant_get_data_as_bytes(value); + GVERBOSE(" %s: %s", name, gsupplicant_format_bytes(wps->key, TRUE)); + } else if (!g_strcmp0(name, "KeyIndex")) { + if (g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) { + wps->key_index = g_variant_get_uint32(value); + GVERBOSE(" %s: %u", name, wps->key_index); + } + } +} + +static +void +gsupplicant_interface_wps_connect_dispose( + GSupplicantInterfaceWPSConnect* connect) +{ + /* May be invoked twice */ + if (connect->wps_proxy) { + gutil_disconnect_handlers(connect->wps_proxy, + connect->wps_proxy_handler_id, + G_N_ELEMENTS(connect->wps_proxy_handler_id)); + g_object_unref(connect->wps_proxy); + connect->wps_proxy = NULL; + } + if (connect->timeout_id) { + g_source_remove(connect->timeout_id); + connect->timeout_id = 0; + } +} + +static +void +gsupplicant_interface_wps_connect_free( + GSupplicantInterfaceWPSConnect* connect) +{ + gsupplicant_interface_wps_connect_dispose(connect); + if (connect->wps.bssid) g_bytes_ref(connect->wps.bssid); + if (connect->wps.p2p_address) g_bytes_ref(connect->wps.p2p_address); + gsupplicant_interface_unref(connect->iface); + if (connect->cancel_id) { + g_cancellable_disconnect(connect->cancel, connect->cancel_id); + } + g_object_unref(connect->cancel); + g_free(connect->pin); + g_free(connect->new_pin); + if (connect->destroy) { + connect->destroy(connect->data); + } + g_slice_free(GSupplicantInterfaceWPSConnect, connect); +} + +static +void +gsupplicant_interface_wps_connect_free1( + void* connect) +{ + gsupplicant_interface_wps_connect_free(connect); +} + +static +void +gsupplicant_interface_wps_connect_cancelled( + GCancellable* cancel, + gpointer data) +{ + /* + * Under GLib < 2.40 this function is invoked under the lock + * protecting cancellable. We can't call g_cancellable_disconnect + * because it wouild deadlock, it has to be invoked on a fresh stack. + * That sucks. + */ + GSupplicantInterfaceWPSConnect* connect = data; + gsupplicant_interface_wps_connect_dispose(connect); + gsupplicant_call_later(gsupplicant_interface_wps_connect_free1, connect); +} + +static +void +gsupplicant_interface_wps_connect_ok( + GSupplicantInterfaceWPSConnect* connect) +{ + GDEBUG("[%s] WPS connect OK", connect->iface->path); + GASSERT(!g_cancellable_is_cancelled(connect->cancel)); + if (connect->fn) { + if (connect->cancel_id) { + /* In case if callback calls g_cancellable_cancel() */ + g_cancellable_disconnect(connect->cancel, connect->cancel_id); + connect->cancel_id = 0; + } + connect->fn(connect->iface, connect->cancel, NULL, connect->new_pin, + connect->data); + } + gsupplicant_interface_wps_connect_free(connect); +} + +static +void +gsupplicant_interface_wps_connect_error_free( + GSupplicantInterfaceWPSConnect* connect, + const GError* error) +{ + GERR("Failed to start WPS: %s", GERRMSG(error)); + if (connect->fn && !g_cancellable_is_cancelled(connect->cancel)) { + GError* tmp_error = NULL; + if (connect->cancel_id) { + /* In case if callback calls g_cancellable_cancel() */ + g_cancellable_disconnect(connect->cancel, connect->cancel_id); + connect->cancel_id = 0; + } + if (!error) { + error = tmp_error = g_error_new_literal(G_IO_ERROR, + G_IO_ERROR_FAILED, "WPS connect failed"); + } + connect->fn(connect->iface, connect->cancel, error, NULL, + connect->data); + if (tmp_error) g_error_free(tmp_error); + } + gsupplicant_interface_wps_connect_free(connect); +} + +static +gboolean +gsupplicant_interface_wps_connect_timeout( + gpointer data) +{ + GSupplicantInterfaceWPSConnect* connect = data; + GDEBUG("WPS connect timed out"); + GASSERT(!g_cancellable_is_cancelled(connect->cancel)); + connect->timeout_id = 0; + if (connect->fn) { + GError* error = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + "WPS connect timed out"); + if (connect->cancel_id) { + /* In case if callback calls g_cancellable_cancel() */ + g_cancellable_disconnect(connect->cancel, connect->cancel_id); + connect->cancel_id = 0; + } + connect->fn(connect->iface, connect->cancel, error, NULL, + connect->data); + g_error_free(error); + } + gsupplicant_interface_wps_connect_free(connect); + return G_SOURCE_REMOVE; +} + +static +GSupplicantInterfaceWPSConnect* +gsupplicant_interface_wps_connect_new( + GSupplicantInterface* iface, + GCancellable* cancel, + const GSupplicantWPSParams* params, + gint timeout_sec, /* 0 = default, negative = no timeout */ + GSupplicantInterfaceStringResultFunc fn, + GDestroyNotify destroy, + void* data) +{ + GSupplicantInterfaceWPSConnect* connect = + g_slice_new0(GSupplicantInterfaceWPSConnect); + connect->iface = gsupplicant_interface_ref(iface); + connect->cancel = cancel ? g_object_ref(cancel) : g_cancellable_new(); + connect->wps.role = params->role; + connect->wps.auth = params->auth; + connect->wps.pin = connect->pin = g_strdup(params->pin); + if (params->bssid) { + connect->wps.bssid = g_bytes_ref(params->bssid); + } + if (params->p2p_address) { + connect->wps.p2p_address = g_bytes_ref(params->p2p_address); + } + if (!timeout_sec) timeout_sec = WPS_DEFAULT_CONNECT_TIMEOUT_SEC; + if (timeout_sec > 0) { + connect->timeout_id = g_timeout_add_seconds(timeout_sec, + gsupplicant_interface_wps_connect_timeout, connect); + } + connect->fn = fn; + connect->destroy = destroy; + connect->data = data; + return connect; +} + +static +void +gsupplicant_interface_wps_connect_proxy_credentials( + FiW1Wpa_supplicant1InterfaceWPS* proxy, + GVariant* args, + gpointer data) +{ + GSupplicantInterfaceWPSConnect* connect = data; + GSupplicantInterface* self = connect->iface; + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantWPSCredentials* wps = &priv->wps_credentials; + GDEBUG("[%s] WPS credentials received", priv->path); + gsupplicant_interface_clear_wps_credentials(self); + gsupplicant_dict_parse(args, gsupplicant_interface_wps_parse_creds, wps); + self->wps_credentials = wps; + priv->pending_signals |= SIGNAL_BIT(WPS_CREDENTIALS); + gsupplicant_interface_emit_pending_signals(self); +} + +static +void +gsupplicant_interface_wps_connect_proxy_event( + FiW1Wpa_supplicant1InterfaceWPS* proxy, + const char* type, + GVariant* args, + gpointer data) +{ + static const GSupNameIntPair event_types[] = { + { "success", WPS_CONNECT_SUCCESS }, + { "fail", WPS_CONNECT_FAIL }, + { "m2d", WPS_CONNECT_M2D }, + { "pbc-overlap", WPS_CONNECT_PBC_OVERLAP } + }; + GSupplicantInterfaceWPSConnect* connect = data; + GDEBUG("[%s] WPS event \"%s\"", connect->iface->path, type); + connect->state = gsupplicant_name_int_get_int(type, event_types, + G_N_ELEMENTS(event_types), WPS_CONNECT_FAIL); + if (connect->state == WPS_CONNECT_SUCCESS) { + /* + * If connect_id is non-zero, then Start() call has completed + * and we have been waiting for this event. Otherwise, we will + * wait for the Start() call to complete. + */ + if (connect->cancel_id) { + gsupplicant_interface_wps_connect_ok(connect); + } + } else { + GError* error = g_error_new(G_IO_ERROR, G_IO_ERROR_FAILED, + "WPS connect failed (%s)", type); + gsupplicant_interface_wps_connect_error_free(connect, error); + g_error_free(error); + } +} + +static +GVariant* +gsupplicant_interface_wps_start_args_new( + const GSupplicantWPSParams* wps) +{ + GVariantBuilder builder; + const gboolean enrollee = (wps->role != GSUPPLICANT_WPS_ROLE_REGISTRAR); + const char* role = enrollee ? "enrollee" : "registrar"; + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + GVERBOSE_("Role: %s", role); + gsupplicant_dict_add_string(&builder, "Role", role); + if (enrollee) { + const char* type = wps->auth == GSUPPLICANT_WPS_AUTH_PIN && wps->pin ? + "pin" : "pbc"; + GVERBOSE_("Type: %s", type); + gsupplicant_dict_add_string(&builder, "Type", type); + } + gsupplicant_dict_add_string0(&builder, "Pin", wps->pin); + gsupplicant_dict_add_bytes0(&builder, "Bssid", wps->bssid); + gsupplicant_dict_add_bytes0(&builder, "P2PDeviceAddress", + wps->p2p_address); + return g_variant_ref_sink(g_variant_builder_end(&builder)); +} + +static +void +gsupplicant_interface_wps_start_pin( + const char* key, + GVariant* value, + void* data) +{ + if (!g_strcmp0(key, "Pin") && + g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + const char** out = data; + *out = g_variant_get_string(value, NULL); + GDEBUG_("pin: %s", *out); + } +} + +/* fi_w1_wpa_supplicant1_interface_wps_call_start() completion */ +static +void +gsupplicant_interface_wps_connect3( + GObject* bus, + GAsyncResult* result, + gpointer data) +{ + GSupplicantInterfaceWPSConnect* connect = data; + GSupplicantInterface* self = connect->iface; + GSupplicantInterfacePriv* priv = self->priv; + GVariant* out; + GError* error = NULL; + if (fi_w1_wpa_supplicant1_interface_wps_call_start_finish( + connect->wps_proxy, &out, result, &error)) { + const char* pin = NULL; + gsupplicant_dict_parse(out, gsupplicant_interface_wps_start_pin, &pin); + connect->new_pin = g_strdup(pin); + if (connect->state == WPS_CONNECT_SUCCESS) { + /* We have already received the WPS event */ + gsupplicant_interface_wps_connect_ok(connect); + } else { + /* Wait for the event */ + GDEBUG("[%s]: Waiting for WPS event", priv->path); + GASSERT(!connect->cancel_id); + connect->cancel_id = g_cancellable_connect(connect->cancel, + G_CALLBACK(gsupplicant_interface_wps_connect_cancelled), + connect, NULL); + } + g_variant_unref(out); + } else { + GDEBUG_("%s %s", priv->path, GERRMSG(error)); + gsupplicant_interface_wps_connect_error_free(connect, error); + g_error_free(error); + } +} + +/* fi_w1_wpa_supplicant1_interface_wps_call_cancel() completion */ +static +void +gsupplicant_interface_wps_connect2( + GObject* object, + GAsyncResult* result, + gpointer data) +{ + GSupplicantInterfaceWPSConnect* connect = data; + GSupplicantInterface* self = connect->iface; + GSupplicantInterfacePriv* priv = self->priv; + GError* error = NULL; + gsupplicant_interface_clear_wps_credentials(self); + gsupplicant_interface_emit_pending_signals(self); + if (fi_w1_wpa_supplicant1_interface_wps_call_cancel_finish( + connect->wps_proxy, result, &error)) { + GVariant* args; + + /* Register to receive WPS events */ + connect->wps_proxy_handler_id[WPS_PROXY_EVENT] = + g_signal_connect(connect->wps_proxy, "event", + G_CALLBACK(gsupplicant_interface_wps_connect_proxy_event), + connect); + connect->wps_proxy_handler_id[WPS_PROXY_CREDENTIALS] = + g_signal_connect(connect->wps_proxy, "credentials", + G_CALLBACK(gsupplicant_interface_wps_connect_proxy_credentials), + connect); + + /* Start WPS configuration */ + args = gsupplicant_interface_wps_start_args_new(&connect->wps); + GDEBUG_("%s starting WPS configuration", priv->path); + fi_w1_wpa_supplicant1_interface_wps_call_start(connect->wps_proxy, + args, connect->cancel, gsupplicant_interface_wps_connect3, + connect); + g_variant_unref(args); + } else { + GDEBUG_("%s %s", priv->path, GERRMSG(error)); + gsupplicant_interface_wps_connect_error_free(connect, error); + g_error_free(error); + } +} + +/* fi_w1_wpa_supplicant1_interface_wps_proxy_new() completion */ +void +gsupplicant_interface_wps_connect1( + GObject* object, + GAsyncResult* result, + gpointer data) +{ + GSupplicantInterfaceWPSConnect* connect = data; + GSupplicantInterface* self = connect->iface; + GSupplicantInterfacePriv* priv = self->priv; + GError* error = NULL; + + GASSERT(!connect->wps_proxy); + connect->wps_proxy = fi_w1_wpa_supplicant1_interface_wps_proxy_new_finish( + result, &error); + if (connect->wps_proxy) { + /* Cancel ongoing WPS operation, if any */ + GVERBOSE_("%s cancelling ongoing WPS operation", priv->path); + fi_w1_wpa_supplicant1_interface_wps_call_cancel(connect->wps_proxy, + connect->cancel, gsupplicant_interface_wps_connect2, connect); + } else { + GDEBUG_("%s %s", priv->path, GERRMSG(error)); + gsupplicant_interface_wps_connect_error_free(connect, error); + g_error_free(error); + } +} + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static +void +gsupplicant_interface_call_finished( + GObject* proxy, + GAsyncResult* result, + gpointer data) +{ + GSupplicantInterfaceCall* call = data; + if (!g_cancellable_is_cancelled(call->cancel)) { + call->finish(call, result); + } else { + /* Generic cleanup */ + GVariant* var = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), + result, NULL); + if (var) { + g_variant_unref(var); + } + } + gsupplicant_interface_unref(call->iface); + g_object_unref(call->cancel); + if (call->destroy) { + call->destroy(call->data); + } + g_slice_free(GSupplicantInterfaceCall, call); +} + +static +GSupplicantInterfaceCall* +gsupplicant_interface_call_new( + GSupplicantInterface* iface, + GCancellable* cancel, + GSupplicantInterfaceCallFinishFunc finish, + GCallback cb, + GDestroyNotify destroy, + void* data) +{ + GSupplicantInterfaceCall* call = g_slice_new0(GSupplicantInterfaceCall); + call->cancel = cancel ? g_object_ref(cancel) : g_cancellable_new(); + call->iface = gsupplicant_interface_ref(iface); + call->finish = finish; + call->fn.cb = cb; + call->destroy = destroy; + call->data = data; + return call; +} + +static +void +gsupplicant_interface_add_network_call_dispose( + GSupplicantInterfaceAddNetworkCall* call) +{ + /* May be invoked twice */ + if (call->network) { + gsupplicant_network_remove_handlers(call->network, + call->network_event_id, G_N_ELEMENTS(call->network_event_id)); + gsupplicant_network_unref(call->network); + call->network = NULL; + } + if (call->args) { + g_variant_unref(call->args); + call->args = NULL; + } +} + +static +void +gsupplicant_interface_add_network_call_free( + GSupplicantInterfaceAddNetworkCall* call) +{ + GASSERT(!call->pending); + gsupplicant_interface_add_network_call_dispose(call); + gsupplicant_interface_unref(call->iface); + if (call->cancel_id) { + g_cancellable_disconnect(call->cancel, call->cancel_id); + } + g_object_unref(call->cancel); + g_free(call->path); + if (call->destroy) { + call->destroy(call->data); + } + g_slice_free(GSupplicantInterfaceAddNetworkCall, call); +} + +static +void +gsupplicant_interface_add_network_call_free1( + void* call) +{ + gsupplicant_interface_add_network_call_free(call); +} + +static +void +gsupplicant_interface_call_add_network_finish( + GSupplicantInterfaceAddNetworkCall* call, + const GError* error) +{ + if (call->fn && !g_cancellable_is_cancelled(call->cancel)) { + if (call->cancel_id) { + /* In case if the callback calls g_cancellable_cancel() */ + g_cancellable_disconnect(call->cancel, call->cancel_id); + call->cancel_id = 0; + } + call->fn(call->iface, call->cancel, error, error ? NULL : call->path, + call->data); + } + gsupplicant_interface_add_network_call_free(call); +} + +static +void +gsupplicant_interface_call_add_network_finish_error( + GSupplicantInterfaceAddNetworkCall* call) +{ + if (call->fn && !g_cancellable_is_cancelled(call->cancel)) { + GError* error = g_error_new(G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to enable %s", call->path); + if (call->cancel_id) { + /* In case if the callback calls g_cancellable_cancel() */ + g_cancellable_disconnect(call->cancel, call->cancel_id); + call->cancel_id = 0; + } + call->fn(call->iface, call->cancel, error, NULL, call->data); + g_error_free(error); + } + gsupplicant_interface_add_network_call_free(call); +} + +static +void +gsupplicant_interface_add_network_call_cancelled( + GCancellable* cancel, + gpointer data) +{ + GSupplicantInterfaceAddNetworkCall* call = data; + GASSERT(call->cancel == cancel); + if (call->pending) { + /* + * An asynchrnous call is pending, we expect it to complete + * with G_IO_ERROR_CANCELLED. + */ + gsupplicant_interface_add_network_call_dispose(call); + } else { + /* + * Under GLib < 2.40 this function is invoked under the lock + * protecting cancellable. We can't call g_cancellable_disconnect + * because it wouild deadlock, it has to be invoked on a fresh stack. + * That sucks. + */ + gsupplicant_call_later(gsupplicant_interface_add_network_call_free1, + call); + } +} + +static +GSupplicantInterfaceAddNetworkCall* +gsupplicant_interface_add_network_call_new( + GSupplicantInterface* iface, + GCancellable* cancel, + const GSupplicantNetworkParams* np, + guint flags, + GSupplicantInterfaceStringResultFunc fn, + GDestroyNotify destroy, + void* data) +{ + GSupplicantInterfaceAddNetworkCall* call = + g_slice_new0(GSupplicantInterfaceAddNetworkCall); + call->cancel = cancel ? g_object_ref(cancel) : g_cancellable_new(); + call->cancel_id = g_cancellable_connect(call->cancel, + G_CALLBACK(gsupplicant_interface_add_network_call_cancelled), + call, NULL); + call->args = gsupplicant_interface_add_network_args_new(np); + call->iface = gsupplicant_interface_ref(iface); + call->fn = fn; + call->destroy = destroy; + call->data = data; + call->flags = flags; + return call; +} + +static +void +gsupplicant_interface_call_finish_void( + GSupplicantInterfaceCall* call, + GAsyncResult* result) +{ + GError* error = NULL; + GDBusProxy* proxy = G_DBUS_PROXY(call->iface->priv->proxy); + GVariant* var = g_dbus_proxy_call_finish(proxy, result, &error); + if (var) { + g_variant_unref(var); + } + if (call->fn.fn_void) { + call->fn.fn_void(call->iface, call->cancel, error, call->data); + } + if (error) { + g_error_free(error); + } +} + +static +void +gsupplicant_interface_call_finish_signal_poll( + GSupplicantInterfaceCall* call, + GAsyncResult* result) +{ + GError* error = NULL; + GVariant* dict = NULL; + GSupplicantSignalPoll info; + const GSupplicantSignalPoll* info_ptr; + FiW1Wpa_supplicant1Interface* proxy = call->iface->priv->proxy; + if (fi_w1_wpa_supplicant1_interface_call_signal_poll_finish(proxy, &dict, + result, &error) && call->fn.fn_signal_poll) { + GVariantIter it; + GVariant* entry; + info_ptr = &info; + memset(&info, 0, sizeof(info)); + if (g_variant_is_of_type(dict, G_VARIANT_TYPE_VARIANT)) { + GVariant* tmp = g_variant_get_variant(dict); + g_variant_unref(dict); + dict = tmp; + } + for (g_variant_iter_init(&it, dict); + (entry = g_variant_iter_next_value(&it)) != NULL; + g_variant_unref(entry)) { + GVariant* key = g_variant_get_child_value(entry, 0); + GVariant* value = g_variant_get_child_value(entry, 1); + const char* name = g_variant_get_string(key, NULL); + if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT)) { + GVariant* tmp = g_variant_get_variant(value); + g_variant_unref(value); + value = tmp; + } + if (!g_strcmp0(name, "linkspeed")) { + info.linkspeed = g_variant_get_int32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_LINKSPEED; + } else if (!g_strcmp0(name, "noise")) { + info.noise = g_variant_get_int32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_NOISE; + } else if (!g_strcmp0(name, "frequency")) { + info.frequency = g_variant_get_uint32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_FREQUENCY; + } else if (!g_strcmp0(name, "rssi")) { + info.rssi = g_variant_get_int32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_RSSI; + } else if (!g_strcmp0(name, "avg-rssi")) { + info.avg_rssi = g_variant_get_int32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_AVG_RSSI; + } else if (!g_strcmp0(name, "center-frq1")) { + info.center_frq1 = g_variant_get_int32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_CENTER_FRQ1; + } else if (!g_strcmp0(name, "center-frq2")) { + info.center_frq2 = g_variant_get_int32(value); + info.flags |= GSUPPLICANT_SIGNAL_POLL_CENTER_FRQ2; + } + g_variant_unref(key); + g_variant_unref(value); + } + g_variant_unref(dict); + } else { + info_ptr = NULL; + } + if (call->fn.fn_signal_poll) { + call->fn.fn_signal_poll(call->iface, call->cancel, error, info_ptr, + call->data); + } + if (error) { + g_error_free(error); + } +} + +static +GCancellable* +gsupplicant_interface_call_void_void( + GSupplicantInterface* self, + GCancellable* cancel, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify destroy, + void* data, + void (*submit)(FiW1Wpa_supplicant1Interface* proxy, + GCancellable* cancel, GAsyncReadyCallback cb, gpointer data)) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceCall* call = gsupplicant_interface_call_new(self, + cancel, gsupplicant_interface_call_finish_void, G_CALLBACK(fn), + destroy, data); + submit(priv->proxy, call->cancel, gsupplicant_interface_call_finished, + call); + return call->cancel; + } + return NULL; +} + +static +GCancellable* +gsupplicant_interface_call_string_void( + GSupplicantInterface* self, + GCancellable* cancel, + const char* arg, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify destroy, + void* data, + void (*submit)(FiW1Wpa_supplicant1Interface* proxy, const gchar *arg, + GCancellable* cancel, GAsyncReadyCallback cb, gpointer data)) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceCall* call = gsupplicant_interface_call_new(self, + cancel, gsupplicant_interface_call_finish_void, G_CALLBACK(fn), + destroy, data); + submit(priv->proxy, arg, call->cancel, + gsupplicant_interface_call_finished, call); + return call->cancel; + } + gsupplicant_cancel_later(cancel); + return NULL; +} + +static +void +gsupplicant_interface_update_valid( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const gboolean valid = priv->proxy && self->supplicant->valid; + if (self->valid != valid) { + self->valid = valid; + GDEBUG("Interface %s is %svalid", priv->path, valid ? "" : "in"); + priv->pending_signals |= SIGNAL_BIT(VALID); + } +} + +static +void +gsupplicant_interface_update_present( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const gboolean present = priv->proxy && self->supplicant->valid && + gutil_strv_contains(self->supplicant->interfaces, priv->path); + if (self->present != present) { + self->present = present; + GDEBUG("interface %s is %spresent", priv->path, present ? "" : "not "); + priv->pending_signals |= SIGNAL_BIT(PRESENT); + } +} + +static +void +gsupplicant_interface_parse_cap( + const char* name, + GVariant* value, + void* data) +{ + GSupplicantInterfaceCaps* caps = data; + if (!g_strcmp0(name, "Pairwise")) { + static const GSupNameIntPair pairwise_map [] = { + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP }, + { "none", GSUPPLICANT_CIPHER_NONE } + }; + caps->pairwise = gsupplicant_parse_bits_array(0, name, value, + pairwise_map, G_N_ELEMENTS(pairwise_map)); + } else if (!g_strcmp0(name, "Group")) { + static const GSupNameIntPair group_map [] = { + { "ccmp", GSUPPLICANT_CIPHER_CCMP }, + { "tkip", GSUPPLICANT_CIPHER_TKIP }, + { "wep104", GSUPPLICANT_CIPHER_WEP104 }, + { "wep40", GSUPPLICANT_CIPHER_WEP40 } + }; + caps->group = gsupplicant_parse_bits_array(0, name, value, + group_map, G_N_ELEMENTS(group_map)); + } else if (!g_strcmp0(name, "KeyMgmt")) { + static const GSupNameIntPair keymgmt_map [] = { + { "wpa-psk", GSUPPLICANT_KEYMGMT_WPA_PSK }, + { "wpa-ft-psk", GSUPPLICANT_KEYMGMT_WPA_FT_PSK }, + { "wpa-psk-sha256", GSUPPLICANT_KEYMGMT_WPA_PSK_SHA256 }, + { "wpa-eap", GSUPPLICANT_KEYMGMT_WPA_EAP }, + { "wpa-ft-eap", GSUPPLICANT_KEYMGMT_WPA_FT_EAP }, + { "wpa-eap-sha256", GSUPPLICANT_KEYMGMT_WPA_EAP_SHA256 }, + { "ieee8021x", GSUPPLICANT_KEYMGMT_IEEE8021X }, + { "wpa-none", GSUPPLICANT_KEYMGMT_WPA_NONE }, + { "wps", GSUPPLICANT_KEYMGMT_WPS }, + { "none", GSUPPLICANT_KEYMGMT_NONE } + }; + caps->keymgmt = gsupplicant_parse_bits_array(0, name, value, + keymgmt_map, G_N_ELEMENTS(keymgmt_map)); + } else if (!g_strcmp0(name, "Protocol")) { + static const GSupNameIntPair protocol_map [] = { + { "rsn", GSUPPLICANT_PROTOCOL_RSN }, + { "wpa", GSUPPLICANT_PROTOCOL_WPA } + }; + caps->protocol = gsupplicant_parse_bits_array(0, name, value, + protocol_map, G_N_ELEMENTS(protocol_map)); + } else if (!g_strcmp0(name, "AuthAlg")) { + static const GSupNameIntPair auth_alg_map [] = { + { "open", GSUPPLICANT_AUTH_OPEN }, + { "shared", GSUPPLICANT_AUTH_SHARED }, + { "leap", GSUPPLICANT_AUTH_LEAP } + }; + caps->auth_alg = gsupplicant_parse_bits_array(0, name, value, + auth_alg_map, G_N_ELEMENTS(auth_alg_map)); + } else if (!g_strcmp0(name, "Scan")) { + static const GSupNameIntPair scan_map [] = { + { "active", GSUPPLICANT_INTERFACE_CAPS_SCAN_ACTIVE }, + { "passive", GSUPPLICANT_INTERFACE_CAPS_SCAN_PASSIVE }, + { "ssid", GSUPPLICANT_INTERFACE_CAPS_SCAN_SSID } + }; + caps->scan = gsupplicant_parse_bits_array(0, name, value, + scan_map, G_N_ELEMENTS(scan_map)); + } else if (!g_strcmp0(name, "Modes")) { + static const GSupNameIntPair modes_map [] = { + { "infrastructure", GSUPPLICANT_INTERFACE_CAPS_MODES_INFRA}, + { "ad-hoc", GSUPPLICANT_INTERFACE_CAPS_MODES_AD_HOC }, + { "ap", GSUPPLICANT_INTERFACE_CAPS_MODES_AP }, + { "p2p", GSUPPLICANT_INTERFACE_CAPS_MODES_P2P } + }; + caps->modes = gsupplicant_parse_bits_array(0, name, value, + modes_map, G_N_ELEMENTS(modes_map)); + } else if (!g_strcmp0(name, "MaxScanSSID")) { + caps->max_scan_ssid = g_variant_get_int32(value); + GVERBOSE(" %s: %d", name, caps->max_scan_ssid); + } else { + GWARN("Unexpected interface capability key %s", name); + } +} + +static +void +gsupplicant_interface_update_caps( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const GSupplicantInterfaceCaps caps = self->caps; + GVariant* dict; + memset(&self->caps, 0, sizeof(self->caps)); + GVERBOSE("[%s] Capabilities:", priv->path); + dict = fi_w1_wpa_supplicant1_interface_get_capabilities(priv->proxy); + gsupplicant_dict_parse(dict, gsupplicant_interface_parse_cap, &self->caps); + if (memcmp(&caps, &self->caps, sizeof(caps))) { + priv->pending_signals |= SIGNAL_BIT(CAPS); + } +} + +static +void +gsupplicant_interface_update_state( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const GSUPPLICANT_INTERFACE_STATE state = gsupplicant_name_int_get_int( + fi_w1_wpa_supplicant1_interface_get_state(priv->proxy), + gsupplicant_interface_states, + G_N_ELEMENTS(gsupplicant_interface_states), + GSUPPLICANT_INTERFACE_STATE_UNKNOWN); + if (self->state != state) { + self->state = state; + priv->pending_signals |= SIGNAL_BIT(STATE); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_STATE, + gsupplicant_interface_state_name(state)); + } +} + +static +void +gsupplicant_interface_update_scanning( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + gboolean b = fi_w1_wpa_supplicant1_interface_get_scanning(priv->proxy); + if (self->scanning != b) { + self->scanning = b; + priv->pending_signals |= SIGNAL_BIT(SCANNING); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_SCANNING, + b ? "true" : "false"); + } +} + +static +void +gsupplicant_interface_update_ap_scan( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const guint val = fi_w1_wpa_supplicant1_interface_get_ap_scan(priv->proxy); + if (self->ap_scan != val) { + self->ap_scan = val; + priv->pending_signals |= SIGNAL_BIT(AP_SCAN); + GVERBOSE("[%s] %s: %u", priv->path, PROXY_PROPERTY_NAME_AP_SCAN, val); + } +} + +static +void +gsupplicant_interface_update_scan_interval( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + gint b = fi_w1_wpa_supplicant1_interface_get_scan_interval(priv->proxy); + if (self->scan_interval != b) { + self->scan_interval = b; + priv->pending_signals |= SIGNAL_BIT(SCAN_INTERVAL); + GVERBOSE("[%s] %s: %d", priv->path, + PROXY_PROPERTY_NAME_SCAN_INTERVAL, b); + } +} + +static +void +gsupplicant_interface_update_country( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const char* country = + fi_w1_wpa_supplicant1_interface_get_country(priv->proxy); + if (g_strcmp0(priv->country, country)) { + g_free(priv->country); + self->country = priv->country = g_strdup(country); + priv->pending_signals |= SIGNAL_BIT(COUNTRY); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_COUNTRY, + self->country); + } +} + +static +void +gsupplicant_interface_update_driver( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const char* driver = + fi_w1_wpa_supplicant1_interface_get_driver(priv->proxy); + if (g_strcmp0(priv->driver, driver)) { + g_free(priv->driver); + self->driver = priv->driver = g_strdup(driver); + priv->pending_signals |= SIGNAL_BIT(DRIVER); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_DRIVER, + self->driver); + } +} + +static +void +gsupplicant_interface_update_ifname( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const char* ifname = + fi_w1_wpa_supplicant1_interface_get_ifname(priv->proxy); + if (g_strcmp0(priv->ifname, ifname)) { + g_free(priv->ifname); + self->ifname = priv->ifname = g_strdup(ifname); + priv->pending_signals |= SIGNAL_BIT(IFNAME); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_IFNAME, + self->ifname); + } +} + +static +void +gsupplicant_interface_update_bridge_ifname( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const char* ifname = + fi_w1_wpa_supplicant1_interface_get_bridge_ifname(priv->proxy); + if (g_strcmp0(priv->bridge_ifname, ifname)) { + g_free(priv->bridge_ifname); + self->bridge_ifname = priv->bridge_ifname = g_strdup(ifname); + priv->pending_signals |= SIGNAL_BIT(BRIDGE_IFNAME); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_BRIDGE_IFNAME, + self->bridge_ifname); + } +} + +static +void +gsupplicant_interface_update_current_bss( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const char* bss = + gsupplicant_interface_association_path_filter( + fi_w1_wpa_supplicant1_interface_get_current_bss(priv->proxy)); + if (g_strcmp0(priv->current_bss, bss)) { + g_free(priv->current_bss); + self->current_bss = priv->current_bss = g_strdup(bss); + priv->pending_signals |= SIGNAL_BIT(CURRENT_BSS); + GVERBOSE("[%s] %s: %s", priv->path, PROXY_PROPERTY_NAME_CURRENT_BSS, + self->current_bss); + } +} + +static +void +gsupplicant_interface_update_current_network( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + const char* network = + gsupplicant_interface_association_path_filter( + fi_w1_wpa_supplicant1_interface_get_current_network(priv->proxy)); + if (g_strcmp0(priv->current_network, network)) { + g_free(priv->current_network); + self->current_network = priv->current_network = g_strdup(network); + priv->pending_signals |= SIGNAL_BIT(CURRENT_NETWORK); + GVERBOSE("[%s] %s: %s", priv->path, + PROXY_PROPERTY_NAME_CURRENT_NETWORK, self->current_network); + } +} + +static +void +gsupplicant_interface_update_bsss( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + /* + * g_variant_get_objv returns shallow copy, i.e. the return + * result should be released with g_free(), but the individual + * strings must not be modified. + */ + gchar** bsss = (char**)(self->valid ? + fi_w1_wpa_supplicant1_interface_get_bsss(priv->proxy) : NULL); + if (!gutil_strv_equal((const GStrV*)bsss, priv->bsss)) { + GStrV* ptr; + for (ptr = bsss; *ptr; ptr++) { + *ptr = g_strdup(*ptr); + } + g_strfreev(priv->bsss); + self->bsss = priv->bsss = bsss; + priv->pending_signals |= SIGNAL_BIT(BSSS); + } else { + g_free(bsss); + } +} + +static +void +gsupplicant_interface_update_networks( + GSupplicantInterface* self) +{ + GSupplicantInterfacePriv* priv = self->priv; + /* + * g_variant_get_objv returns shallow copy, i.e. the return + * result should be released with g_free(), but the individual + * strings must not be modified. + */ + gchar** networks = (char**)(self->valid ? + fi_w1_wpa_supplicant1_interface_get_networks(priv->proxy) : NULL); + if (!gutil_strv_equal((const GStrV*)networks, priv->networks)) { + GStrV* ptr; + for (ptr = networks; *ptr; ptr++) { + *ptr = g_strdup(*ptr); + } + g_strfreev(priv->networks); + self->networks = priv->networks = networks; + priv->pending_signals |= SIGNAL_BIT(NETWORKS); + } else { + g_free(networks); + } +} + +static +void +gsupplicant_interface_proxy_gproperties_changed( + FiW1Wpa_supplicant1Interface* proxy, + GVariant* changed, + GStrv invalidated, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + if (invalidated) { + char** ptr; + for (ptr = invalidated; *ptr; ptr++) { + const char* name = *ptr; + if (!strcmp(name, PROXY_PROPERTY_NAME_STATE)) { + if (self->state != GSUPPLICANT_INTERFACE_STATE_UNKNOWN) { + self->state = GSUPPLICANT_INTERFACE_STATE_UNKNOWN; + priv->pending_signals |= SIGNAL_BIT(STATE); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_SCANNING)) { + if (self->scanning) { + self->scanning = FALSE; + priv->pending_signals |= SIGNAL_BIT(SCANNING); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_AP_SCAN)) { + if (self->ap_scan) { + self->ap_scan = 0; + priv->pending_signals |= SIGNAL_BIT(AP_SCAN); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_CAPABILITIES)) { + const GSupplicantInterfaceCaps caps = self->caps; + memset(&self->caps, 0, sizeof(self->caps)); + if (memcmp(&caps, &self->caps, sizeof(caps))) { + priv->pending_signals |= SIGNAL_BIT(CAPS); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_COUNTRY)) { + if (self->country) { + self->country = NULL; + priv->pending_signals |= SIGNAL_BIT(COUNTRY); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_DRIVER)) { + if (self->driver) { + self->driver = NULL; + priv->pending_signals |= SIGNAL_BIT(DRIVER); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_IFNAME)) { + if (self->ifname) { + self->ifname = NULL; + priv->pending_signals |= SIGNAL_BIT(IFNAME); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_BRIDGE_IFNAME)) { + if (self->bridge_ifname) { + self->bridge_ifname = NULL; + priv->pending_signals |= SIGNAL_BIT(BRIDGE_IFNAME); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_CURRENT_BSS)) { + if (self->current_bss) { + self->current_bss = NULL; + priv->pending_signals |= SIGNAL_BIT(CURRENT_BSS); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_CURRENT_NETWORK)) { + if (self->current_network) { + self->current_network = NULL; + priv->pending_signals |= SIGNAL_BIT(CURRENT_NETWORK); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_SCAN_INTERVAL)) { + if (self->scan_interval) { + self->scan_interval = 0; + priv->pending_signals |= SIGNAL_BIT(SCAN_INTERVAL); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_BSSS)) { + if (priv->bsss) { + g_strfreev(priv->bsss); + self->bsss = priv->bsss = NULL; + priv->pending_signals |= SIGNAL_BIT(BSSS); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_NETWORKS)) { + if (priv->networks) { + g_strfreev(priv->networks); + self->networks = priv->networks = NULL; + priv->pending_signals |= SIGNAL_BIT(NETWORKS); + } + } + } + } + if (changed) { + GVariantIter it; + GVariant* value; + const char* name; + g_variant_iter_init(&it, changed); + while (g_variant_iter_next(&it, "{&sv}", &name, &value)) { + if (!strcmp(name, PROXY_PROPERTY_NAME_STATE)) { + gsupplicant_interface_update_state(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_SCANNING)) { + gsupplicant_interface_update_scanning(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_AP_SCAN)) { + gsupplicant_interface_update_ap_scan(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_SCAN_INTERVAL)) { + gsupplicant_interface_update_scan_interval(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_CAPABILITIES)) { + gsupplicant_interface_update_caps(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_COUNTRY)) { + gsupplicant_interface_update_country(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_DRIVER)) { + gsupplicant_interface_update_driver(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_IFNAME)) { + gsupplicant_interface_update_ifname(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_BRIDGE_IFNAME)) { + gsupplicant_interface_update_bridge_ifname(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_CURRENT_BSS)) { + gsupplicant_interface_update_current_bss(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_CURRENT_NETWORK)) { + gsupplicant_interface_update_current_network(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_BSSS)) { + gsupplicant_interface_update_bsss(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_NETWORKS)) { + gsupplicant_interface_update_networks(self); + } + g_variant_unref(value); + } + } + gsupplicant_interface_emit_pending_signals(self); +} + +static +void +gsupplicant_interface_proxy_properties_changed( + FiW1Wpa_supplicant1Interface* proxy, + GVariant* change, + gpointer data) +{ + gsupplicant_interface_proxy_gproperties_changed(proxy, change, NULL, data); +} + +static +void +gsupplicant_interface_proxy_bss_added( + FiW1Wpa_supplicant1Interface* proxy, + const char* path, + GVariant* properties, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + GDEBUG("BSS added: %s", path); + if (!gutil_strv_contains(priv->bsss, path)) { + self->bsss = priv->bsss = gutil_strv_add(priv->bsss, path); + priv->pending_signals |= SIGNAL_BIT(BSSS); + gsupplicant_interface_emit_pending_signals(self); + } +} + +static +void +gsupplicant_interface_proxy_bss_removed( + FiW1Wpa_supplicant1Interface* proxy, + const char* path, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + const int pos = gutil_strv_find(priv->bsss, path); + GDEBUG("BSS removed: %s", path); + if (pos >= 0) { + self->bsss = priv->bsss = gutil_strv_remove_at(priv->bsss, pos, TRUE); + priv->pending_signals |= SIGNAL_BIT(BSSS); + gsupplicant_interface_emit_pending_signals(self); + } +} + +static +void +gsupplicant_interface_proxy_network_added( + FiW1Wpa_supplicant1Interface* proxy, + const char* path, + GVariant* properties, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + GDEBUG("Network added: %s", path); + if (!gutil_strv_contains(priv->networks, path)) { + self->networks = priv->networks = gutil_strv_add(priv->networks, path); + priv->pending_signals |= SIGNAL_BIT(NETWORKS); + gsupplicant_interface_emit_pending_signals(self); + } +} + +static +void +gsupplicant_interface_proxy_network_removed( + FiW1Wpa_supplicant1Interface* proxy, + const char* path, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + const int pos = gutil_strv_find(priv->networks, path); + GDEBUG("Network removed: %s", path); + if (pos >= 0) { + self->networks = priv->networks = + gutil_strv_remove_at(priv->networks, pos, TRUE); + priv->pending_signals |= SIGNAL_BIT(NETWORKS); + gsupplicant_interface_emit_pending_signals(self); + } +} + +static +void +gsupplicant_interface_proxy_network_selected( + FiW1Wpa_supplicant1Interface* proxy, + const char* path, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + GDEBUG("Network selected: %s", path); + if (g_strcmp0(priv->current_network, path)) { + g_free(priv->current_network); + self->current_network = priv->current_network = g_strdup(path); + priv->pending_signals |= SIGNAL_BIT(CURRENT_NETWORK); + gsupplicant_interface_emit_pending_signals(self); + } +} + +static +void +gsupplicant_interface_supplicant_valid_changed( + GSupplicant* supplicant, + void* data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GASSERT(self->supplicant == supplicant); + gsupplicant_interface_update_valid(self); + gsupplicant_interface_update_present(self); + gsupplicant_interface_emit_pending_signals(self); +} + +static +void +gsupplicant_interface_supplicant_interfaces_changed( + GSupplicant* supplicant, + void* data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GASSERT(self->supplicant == supplicant); + gsupplicant_interface_update_present(self); + gsupplicant_interface_emit_pending_signals(self); +} + +static +void +gsupplicant_interface_create2( + GObject* bus, + GAsyncResult* result, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + GError* error = NULL; + GASSERT(!self->valid); + GASSERT(!priv->proxy); + priv->proxy = fi_w1_wpa_supplicant1_interface_proxy_new_for_bus_finish( + result, &error); + if (priv->proxy) { + priv->proxy_handler_id[PROXY_GPROPERTIES_CHANGED] = + g_signal_connect(priv->proxy, "g-properties-changed", + G_CALLBACK(gsupplicant_interface_proxy_gproperties_changed), self); + priv->proxy_handler_id[PROXY_PROPERTIES_CHANGED] = + g_signal_connect(priv->proxy, "properties-changed", + G_CALLBACK(gsupplicant_interface_proxy_properties_changed), self); + priv->proxy_handler_id[PROXY_BSS_ADDED] = + g_signal_connect(priv->proxy, "bssadded", + G_CALLBACK(gsupplicant_interface_proxy_bss_added), self); + priv->proxy_handler_id[PROXY_BSS_REMOVED] = + g_signal_connect(priv->proxy, "bssremoved", + G_CALLBACK(gsupplicant_interface_proxy_bss_removed), self); + priv->proxy_handler_id[PROXY_NETWORK_ADDED] = + g_signal_connect(priv->proxy, "network-added", + G_CALLBACK(gsupplicant_interface_proxy_network_added), self); + priv->proxy_handler_id[PROXY_NETWORK_REMOVED] = + g_signal_connect(priv->proxy, "network-removed", + G_CALLBACK(gsupplicant_interface_proxy_network_removed), self); + priv->proxy_handler_id[PROXY_NETWORK_SELECTED] = + g_signal_connect(priv->proxy, "network-selected", + G_CALLBACK(gsupplicant_interface_proxy_network_selected), self); + + priv->supplicant_handler_id[SUPPLICANT_VALID_CHANGED] = + gsupplicant_add_handler(self->supplicant, + GSUPPLICANT_PROPERTY_VALID, + gsupplicant_interface_supplicant_valid_changed, self); + priv->supplicant_handler_id[SUPPLICANT_INTERFACES_CHANGED] = + gsupplicant_add_handler(self->supplicant, + GSUPPLICANT_PROPERTY_INTERFACES, + gsupplicant_interface_supplicant_interfaces_changed, self); + + gsupplicant_interface_update_valid(self); + gsupplicant_interface_update_present(self); + gsupplicant_interface_update_caps(self); + gsupplicant_interface_update_state(self); + gsupplicant_interface_update_scanning(self); + gsupplicant_interface_update_ap_scan(self); + gsupplicant_interface_update_scan_interval(self); + gsupplicant_interface_update_country(self); + gsupplicant_interface_update_driver(self); + gsupplicant_interface_update_ifname(self); + gsupplicant_interface_update_bridge_ifname(self); + gsupplicant_interface_update_current_bss(self); + gsupplicant_interface_update_current_network(self); + gsupplicant_interface_update_bsss(self); + gsupplicant_interface_update_networks(self); + + gsupplicant_interface_emit_pending_signals(self); + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + gsupplicant_interface_unref(self); +} + +static +void +gsupplicant_interface_create1( + GObject* bus, + GAsyncResult* result, + gpointer data) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(data); + GSupplicantInterfacePriv* priv = self->priv; + GError* error = NULL; + priv->bus = g_bus_get_finish(result, &error); + if (priv->bus) { + fi_w1_wpa_supplicant1_interface_proxy_new(priv->bus, + G_DBUS_PROXY_FLAGS_NONE, GSUPPLICANT_SERVICE, priv->path, NULL, + gsupplicant_interface_create2, gsupplicant_interface_ref(self)); + } else { + GERR("[%s] %s", priv->path, error->message); + g_error_free(error); + } + gsupplicant_interface_unref(self); +} + +static +void +gsupplicant_interface_destroyed( + gpointer key, + GObject* dead) +{ + GVERBOSE_("%s", (char*)key); + GASSERT(gsupplicant_interface_table); + if (gsupplicant_interface_table) { + GASSERT(g_hash_table_lookup(gsupplicant_interface_table, key) == dead); + g_hash_table_remove(gsupplicant_interface_table, key); + if (g_hash_table_size(gsupplicant_interface_table) == 0) { + g_hash_table_unref(gsupplicant_interface_table); + gsupplicant_interface_table = NULL; + } + } +} + +static +GSupplicantInterface* +gsupplicant_interface_create( + const char* path) +{ + GSupplicantInterface* self = g_object_new(GSUPPLICANT_INTERFACE_TYPE,NULL); + GSupplicantInterfacePriv* priv = self->priv; + self->supplicant = gsupplicant_new(); + self->path = priv->path = g_strdup(path); + g_bus_get(GSUPPLICANT_BUS_TYPE, NULL, gsupplicant_interface_create1, + gsupplicant_interface_ref(self)); + return self; +} + +/*==========================================================================* + * API + *==========================================================================*/ + +GSupplicantInterface* +gsupplicant_interface_new( + const char* path) +{ + GSupplicantInterface* self = NULL; + if (G_LIKELY(path)) { + self = gsupplicant_interface_table ? + gsupplicant_interface_ref(g_hash_table_lookup( + gsupplicant_interface_table, path)) : NULL; + if (!self) { + gpointer key = g_strdup(path); + self = gsupplicant_interface_create(path); + if (!gsupplicant_interface_table) { + gsupplicant_interface_table = + g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + } + g_hash_table_replace(gsupplicant_interface_table, key, self); + g_object_weak_ref(G_OBJECT(self), gsupplicant_interface_destroyed, + key); + } + } + return self; +} + +GSupplicantInterface* +gsupplicant_interface_ref( + GSupplicantInterface* self) +{ + if (G_LIKELY(self)) { + g_object_ref(GSUPPLICANT_INTERFACE(self)); + return self; + } else { + return NULL; + } +} + +void +gsupplicant_interface_unref( + GSupplicantInterface* self) +{ + if (G_LIKELY(self)) { + g_object_unref(GSUPPLICANT_INTERFACE(self)); + } +} + +gulong +gsupplicant_interface_add_property_changed_handler( + GSupplicantInterface* self, + GSUPPLICANT_INTERFACE_PROPERTY property, + GSupplicantInterfacePropertyFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signal_name; + char buf[sizeof(SIGNAL_PROPERTY_CHANGED_NAME) + 2 + + SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN]; + if (property) { + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_NAME "::" + SIGNAL_PROPERTY_CHANGED_DETAIL, property); + buf[sizeof(buf)-1] = 0; + signal_name = buf; + } else { + signal_name = SIGNAL_PROPERTY_CHANGED_NAME; + } + return g_signal_connect(self, signal_name, G_CALLBACK(fn), data); + } + return 0; +} + +gulong +gsupplicant_interface_add_handler( + GSupplicantInterface* self, + GSUPPLICANT_INTERFACE_PROPERTY prop, + GSupplicantInterfaceFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signame; + switch (prop) { +#define SIGNAL_NAME_(P,p) case GSUPPLICANT_INTERFACE_PROPERTY_##P: \ + signame = gsupplicant_interface_signame[SIGNAL_##P##_CHANGED]; \ + break; + GSUPPLICANT_INTERFACE_PROPERTIES_(SIGNAL_NAME_) + default: + signame = NULL; + break; + } + if (G_LIKELY(signame)) { + return g_signal_connect(self, signame, G_CALLBACK(fn), data); + } + } + return 0; +} + +gboolean +gsupplicant_interface_set_ap_scan( + GSupplicantInterface* self, + guint ap_scan) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfacePriv* priv = self->priv; + fi_w1_wpa_supplicant1_interface_set_ap_scan(priv->proxy, ap_scan); + return TRUE; + } + return FALSE; +} + +gboolean +gsupplicant_interface_set_country( + GSupplicantInterface* self, + const char* country) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfacePriv* priv = self->priv; + if (!country) country = ""; + fi_w1_wpa_supplicant1_interface_set_country(priv->proxy, country); + return TRUE; + } + return FALSE; +} + +void +gsupplicant_interface_remove_handler( + GSupplicantInterface* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +gsupplicant_interface_remove_handlers( + GSupplicantInterface* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +GCancellable* +gsupplicant_interface_disconnect( + GSupplicantInterface* self, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_call_void_void(self, NULL, fn, NULL, data, + fi_w1_wpa_supplicant1_interface_call_disconnect); +} + +GCancellable* +gsupplicant_interface_reassociate( + GSupplicantInterface* self, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_call_void_void(self, NULL, fn, NULL, data, + fi_w1_wpa_supplicant1_interface_call_reassociate); +} + +GCancellable* +gsupplicant_interface_reconnect( + GSupplicantInterface* self, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_call_void_void(self, NULL, fn, NULL, data, + fi_w1_wpa_supplicant1_interface_call_reconnect); +} + +GCancellable* +gsupplicant_interface_reattach( + GSupplicantInterface* self, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_call_void_void(self, NULL, fn, NULL, data, + fi_w1_wpa_supplicant1_interface_call_reattach); +} + +static /* should be public? */ +GCancellable* +gsupplicant_interface_select_network_full( + GSupplicantInterface* self, + GCancellable* cancel, + const char* path, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify free, + void* data) +{ + if (path && g_variant_is_object_path(path)) { + return gsupplicant_interface_call_string_void(self, cancel, path, fn, + free, data,fi_w1_wpa_supplicant1_interface_call_select_network); + } + gsupplicant_cancel_later(cancel); + return NULL; +} + +GCancellable* +gsupplicant_interface_select_network( + GSupplicantInterface* self, + const char* path, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_select_network_full(self, NULL, path, fn, + NULL, data); +} + +static /* should be public? */ +GCancellable* +gsupplicant_interface_remove_network_full( + GSupplicantInterface* self, + GCancellable* cancel, + const char* path, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify free, + void* data) +{ + if (path && g_variant_is_object_path(path)) { + return gsupplicant_interface_call_string_void(self, cancel, path, fn, + free, data, fi_w1_wpa_supplicant1_interface_call_remove_network); + } + gsupplicant_cancel_later(cancel); + return NULL; +} + +GCancellable* +gsupplicant_interface_remove_network( + GSupplicantInterface* self, + const char* path, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_remove_network_full(self, NULL, path, fn, + NULL, data); +} + +GCancellable* +gsupplicant_interface_remove_all_networks( + GSupplicantInterface* self, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_remove_all_networks_full(self, NULL, fn, + NULL, data); +} + +GCancellable* +gsupplicant_interface_remove_all_networks_full( + GSupplicantInterface* self, + GCancellable* cancel, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify destroy, + void* data) +{ + return gsupplicant_interface_call_void_void(self, cancel, fn, destroy, + data, fi_w1_wpa_supplicant1_interface_call_remove_all_networks); +} + +GCancellable* +gsupplicant_interface_scan( + GSupplicantInterface* self, + const GSupplicantScanParams* params, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantScanParams default_params; + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceCall* call = gsupplicant_interface_call_new(self, + NULL, gsupplicant_interface_call_finish_void, G_CALLBACK(fn), + NULL, data); + GVariantBuilder builder; + GVariant* dict; + + /* Do passive scan by default */ + if (!params) { + memset(&default_params, 0, sizeof(default_params)); + default_params.type = GSUPPLICANT_SCAN_TYPE_PASSIVE; + params = &default_params; + } + + /* Prepare scan parameters */ + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + gsupplicant_dict_add_string(&builder, "Type", + params->type == GSUPPLICANT_SCAN_TYPE_ACTIVE ? + "active" : "passive"); + if (params->ssids) { + gsupplicant_dict_add_value(&builder, "SSIDs", + gsupplicant_variant_new_ayy(params->ssids)); + } + if (params->ies) { + gsupplicant_dict_add_value(&builder, "IEs", + gsupplicant_variant_new_ayy(params->ies)); + } + if (params->channels) { + guint i; + const GSupplicantScanFrequency* freq = params->channels->freq; + GVariantBuilder auu; + g_variant_builder_init(&auu, G_VARIANT_TYPE("a(uu)")); + for (i=0; ichannels->count; i++, freq++) { + g_variant_builder_add(&auu, "(uu)", freq->center, freq->width); + } + gsupplicant_dict_add_value(&builder, "Channels", + g_variant_builder_end(&auu)); + } + if (params->flags & GSUPPLICANT_SCAN_PARAM_ALLOW_ROAM) { + gsupplicant_dict_add_boolean(&builder, "AllowRoam", + params->allow_roam); + } + + /* Submit the call */ + dict = g_variant_ref_sink(g_variant_builder_end(&builder)); + fi_w1_wpa_supplicant1_interface_call_scan(priv->proxy, dict, + call->cancel, gsupplicant_interface_call_finished, call); + g_variant_unref(dict); + return call->cancel; + } + return NULL; +} + +static /* should be public? */ +GCancellable* +gsupplicant_interface_auto_scan_full( + GSupplicantInterface* self, + GCancellable* cancel, + const char* param, + GSupplicantInterfaceResultFunc fn, + GDestroyNotify destroy, + void* data) +{ + if (!param) param = ""; + return gsupplicant_interface_call_string_void(self, cancel, param, fn, + destroy, data, fi_w1_wpa_supplicant1_interface_call_auto_scan); +} + +GCancellable* +gsupplicant_interface_auto_scan( + GSupplicantInterface* self, + const char* param, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + return gsupplicant_interface_auto_scan_full(self, NULL, param, fn, + NULL, data); +} + +GCancellable* +gsupplicant_interface_remove_flush_bss( + GSupplicantInterface* self, + guint age, + GSupplicantInterfaceResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceCall* call = gsupplicant_interface_call_new(self, + NULL, gsupplicant_interface_call_finish_void, G_CALLBACK(fn), + NULL, data); + fi_w1_wpa_supplicant1_interface_call_flush_bss(priv->proxy, age, + call->cancel, gsupplicant_interface_call_finished, call); + return call->cancel; + } + return NULL; +} + +GCancellable* +gsupplicant_interface_signal_poll( + GSupplicantInterface* self, + GSupplicantInterfaceSignalPollResultFunc fn, + void* data) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceCall* call = gsupplicant_interface_call_new(self, + NULL, gsupplicant_interface_call_finish_signal_poll, + G_CALLBACK(fn), NULL, data); + fi_w1_wpa_supplicant1_interface_call_signal_poll(priv->proxy, + call->cancel, gsupplicant_interface_call_finished, call); + return call->cancel; + } + return NULL; +} + +static +void +gsupplicant_interface_add_network6( + GSupplicantNetwork* network, + void* data) +{ + GSupplicantInterfaceAddNetworkCall* call = data; + if (network->enabled) { + GVERBOSE_("enabled %s", call->path); + gsupplicant_interface_call_add_network_finish(call, NULL); + } +} + +static +gboolean +gsupplicant_interface_add_network5( + GSupplicantInterfaceAddNetworkCall* call, + GError** error) +{ + gboolean done = TRUE; + GASSERT(call->network->valid); + if (!call->network->enabled) { + /* Have to wait for the network to become enabled */ + if (!call->network_event_id[ADD_NETWORK_ENABLED_CHANGED]) { + call->network_event_id[ADD_NETWORK_ENABLED_CHANGED] = + gsupplicant_network_add_handler(call->network, + GSUPPLICANT_NETWORK_PROPERTY_ENABLED, + gsupplicant_interface_add_network6, call); + if (gsupplicant_network_set_enabled(call->network, TRUE)) { + GVERBOSE_("waiting for %s to become enabled", call->path); + done = FALSE; + } else { + g_propagate_error(error, g_error_new(G_IO_ERROR, + G_IO_ERROR_FAILED, "Failed to enable %s", call->path)); + } + } + } + return done; +} + +static +void +gsupplicant_interface_add_network4( + GSupplicantNetwork* network, + void* data) +{ + GSupplicantInterfaceAddNetworkCall* call = data; + GVERBOSE_("%s has become %svalid", call->path, network->valid ? "" : "in"); + if (network->valid) { + GError* error = NULL; + gboolean done = gsupplicant_interface_add_network5(call, &error); + if (done) { + gsupplicant_interface_call_add_network_finish(call, error); + } + if (error) { + g_error_free(error); + } + } else { + gsupplicant_interface_call_add_network_finish_error(call); + } +} + +static +gboolean +gsupplicant_interface_add_network3( + GSupplicantInterfaceAddNetworkCall* call, + GError** error) +{ + if (!call->network->valid) { + /* Have to wait for the network to initialize */ + GVERBOSE_("waiting for %s to initialize", call->path); + if (!call->network_event_id[ADD_NETWORK_VALID_CHANGED]) { + call->network_event_id[ADD_NETWORK_VALID_CHANGED] = + gsupplicant_network_add_handler(call->network, + GSUPPLICANT_NETWORK_PROPERTY_VALID, + gsupplicant_interface_add_network4, call); + } + return FALSE; + } else { + return gsupplicant_interface_add_network5(call, error); + } +} + +static +void +gsupplicant_interface_add_network2( + GObject* obj, + GAsyncResult* result, + gpointer data) +{ + GError* error = NULL; + gboolean done = TRUE; + GSupplicantInterfaceAddNetworkCall* call = data; + FiW1Wpa_supplicant1Interface* proxy = call->iface->priv->proxy; + call->pending = FALSE; + GASSERT(proxy == FI_W1_WPA_SUPPLICANT1_INTERFACE(obj)); + if (fi_w1_wpa_supplicant1_interface_call_select_network_finish(proxy, + result, &error)) { + /* Network has been successfully selected */ + GVERBOSE_("selected %s", call->path); + if (call->flags & GSUPPLICANT_ADD_NETWORK_ENABLE) { + done = gsupplicant_interface_add_network3(call, &error); + } + } + if (done) { + gsupplicant_interface_call_add_network_finish(call, error); + } + if (error) { + g_error_free(error); + } +} + +static +void +gsupplicant_interface_add_network1( + GObject* obj, + GAsyncResult* result, + gpointer data) +{ + GError* error = NULL; + gboolean done = TRUE; + GSupplicantInterfaceAddNetworkCall* call = data; + FiW1Wpa_supplicant1Interface* proxy = call->iface->priv->proxy; + call->pending = FALSE; + GASSERT(proxy == FI_W1_WPA_SUPPLICANT1_INTERFACE(obj)); + if (fi_w1_wpa_supplicant1_interface_call_add_network_finish(proxy, + &call->path, result, &error)) { + GVERBOSE_("added %s", call->path); + if (call->flags & GSUPPLICANT_ADD_NETWORK_ENABLE) { + /* We will need GSupplicantNetwork */ + call->network = gsupplicant_network_new(call->path); + } + if (call->flags & GSUPPLICANT_ADD_NETWORK_SELECT) { + /* + * Select the network first. Also, while it's being selected, + * the GSupplicantNetwork will become valid. + */ + call->pending = TRUE; + fi_w1_wpa_supplicant1_interface_call_select_network(proxy, + call->path, call->cancel, + gsupplicant_interface_add_network2, call); + done = FALSE; + } else if (call->flags & GSUPPLICANT_ADD_NETWORK_ENABLE) { + /* Need to enable the network without selecting it */ + done = gsupplicant_interface_add_network3(call, &error); + } + } + if (done) { + gsupplicant_interface_call_add_network_finish(call, error); + } + if (error) { + g_error_free(error); + } +} + +static +void +gsupplicant_interface_add_network_0( + GObject* obj, + GAsyncResult* result, + gpointer data) +{ + GError* error = NULL; + GSupplicantInterfaceAddNetworkCall* call = data; + FiW1Wpa_supplicant1Interface* proxy = call->iface->priv->proxy; + GASSERT(proxy == FI_W1_WPA_SUPPLICANT1_INTERFACE(obj)); + if (fi_w1_wpa_supplicant1_interface_call_remove_all_networks_finish(proxy, + result, &error)) { + GVERBOSE_("removed all networks"); + call->pending = TRUE; + fi_w1_wpa_supplicant1_interface_call_add_network(proxy, call->args, + call->cancel, gsupplicant_interface_add_network1, call); + g_variant_unref(call->args); + call->args = NULL; + } else { + call->pending = FALSE; + gsupplicant_interface_call_add_network_finish(call, error); + } + if (error) { + g_error_free(error); + } +} + +GCancellable* +gsupplicant_interface_add_network_full( + GSupplicantInterface* self, + GCancellable* cancel, + const GSupplicantNetworkParams* np, + guint flags, + GSupplicantInterfaceStringResultFunc fn, + GDestroyNotify destroy, + void* data) +{ + if (G_LIKELY(self) && self->valid && np) { + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceAddNetworkCall* call = + gsupplicant_interface_add_network_call_new(self, cancel, np, + flags, fn, destroy, data); + call->pending = TRUE; + if (flags & GSUPPLICANT_ADD_NETWORK_DELETE_OTHER) { + fi_w1_wpa_supplicant1_interface_call_remove_all_networks( + priv->proxy, call->cancel, gsupplicant_interface_add_network_0, + call); + } else { + fi_w1_wpa_supplicant1_interface_call_add_network(priv->proxy, + call->args, call->cancel, gsupplicant_interface_add_network1, + call); + g_variant_unref(call->args); + call->args = NULL; + } + return call->cancel; + } + gsupplicant_cancel_later(cancel); + return NULL; +} + +GCancellable* +gsupplicant_interface_add_network( + GSupplicantInterface* self, + const GSupplicantNetworkParams* np, + guint flags, + GSupplicantInterfaceStringResultFunc fn, + void* data) +{ + return gsupplicant_interface_add_network_full(self, NULL, np, flags, fn, + NULL, data); +} + +GCancellable* +gsupplicant_interface_wps_connect_full( + GSupplicantInterface* self, + GCancellable* cancel, + const GSupplicantWPSParams* params, + gint timeout_sec, /* 0 = default, negative = no timeout */ + GSupplicantInterfaceStringResultFunc fn, + GDestroyNotify destroy, + void* data) +{ + if (G_LIKELY(self) && self->valid && params) { + GSupplicantInterfacePriv* priv = self->priv; + GSupplicantInterfaceWPSConnect* connect = + gsupplicant_interface_wps_connect_new(self, cancel, params, + timeout_sec, fn, destroy, data); + GVERBOSE_("%s creating WPS proxy", priv->path); + fi_w1_wpa_supplicant1_interface_wps_proxy_new(priv->bus, + G_DBUS_PROXY_FLAGS_NONE, GSUPPLICANT_SERVICE, priv->path, + connect->cancel, gsupplicant_interface_wps_connect1, connect); + return connect->cancel; + } + gsupplicant_cancel_later(cancel); + return NULL; +} + +GCancellable* +gsupplicant_interface_wps_connect( + GSupplicantInterface* self, + const GSupplicantWPSParams* params, + GSupplicantInterfaceStringResultFunc fn, + void* data) +{ + return gsupplicant_interface_wps_connect_full(self, NULL, params, 0, fn, + NULL, data); +} + +const char* +gsupplicant_interface_state_name( + GSUPPLICANT_INTERFACE_STATE state) +{ + return gsupplicant_name_int_find_int(state, gsupplicant_interface_states, + G_N_ELEMENTS(gsupplicant_interface_states)); +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +/** + * Per instance initializer + */ +static +void +gsupplicant_interface_init( + GSupplicantInterface* self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, GSUPPLICANT_INTERFACE_TYPE, + GSupplicantInterfacePriv); +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +gsupplicant_interface_dispose( + GObject* object) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(object); + GSupplicantInterfacePriv* priv = self->priv; + gsupplicant_interface_clear_wps_credentials(self); + if (priv->proxy) { + gutil_disconnect_handlers(priv->proxy, priv->proxy_handler_id, + G_N_ELEMENTS(priv->proxy_handler_id)); + g_object_unref(priv->proxy); + priv->proxy = NULL; + } + gsupplicant_remove_handlers(self->supplicant, priv->supplicant_handler_id, + G_N_ELEMENTS(priv->supplicant_handler_id)); + if (priv->bus) { + g_object_unref(priv->bus); + priv->bus = NULL; + } + G_OBJECT_CLASS(SUPER_CLASS)->dispose(object); +} + +/** + * Final stage of deinitialization + */ +static +void +gsupplicant_interface_finalize( + GObject* object) +{ + GSupplicantInterface* self = GSUPPLICANT_INTERFACE(object); + GSupplicantInterfacePriv* priv = self->priv; + GASSERT(!priv->bus); + GASSERT(!priv->proxy); + g_strfreev(priv->bsss); + g_strfreev(priv->networks); + g_free(priv->path); + g_free(priv->country); + g_free(priv->driver); + g_free(priv->ifname); + g_free(priv->bridge_ifname); + g_free(priv->current_bss); + g_free(priv->current_network); + gsupplicant_unref(self->supplicant); + G_OBJECT_CLASS(SUPER_CLASS)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +gsupplicant_interface_class_init( + GSupplicantInterfaceClass* klass) +{ + int i; + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = gsupplicant_interface_dispose; + object_class->finalize = gsupplicant_interface_finalize; + g_type_class_add_private(klass, sizeof(GSupplicantInterfacePriv)); + for (i=0; i + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_LOG_H +#define GSUPPLICANT_LOG_H + +#include "gsupplicant_types.h" + +#define GLOG_MODULE_NAME GSUPPLICANT_LOG_MODULE +#include + +#endif /* GSUPPLICANT_LOG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/gsupplicant_network.c b/src/gsupplicant_network.c new file mode 100644 index 0000000..5d996f8 --- /dev/null +++ b/src/gsupplicant_network.c @@ -0,0 +1,773 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant_network.h" +#include "gsupplicant_interface.h" +#include "gsupplicant_dbus.h" +#include "gsupplicant_log.h" + +#include +#include + +/* Generated headers */ +#include "fi.w1.wpa_supplicant1.Network.h" + +/* Object definition */ +enum supplicant_network_proxy_handler_id { + PROXY_GPROPERTIES_CHANGED, + PROXY_PROPERTIES_CHANGED, + PROXY_HANDLER_COUNT +}; + +enum supplicant_interface_handler_id { + INTERFACE_VALID_CHANGED, + INTERFACE_NETWORKS_CHANGED, + INTERFACE_HANDLER_COUNT +}; + +struct gsupplicant_network_priv { + FiW1Wpa_supplicant1Network* proxy; + gulong proxy_handler_id[PROXY_HANDLER_COUNT]; + gulong iface_handler_id[INTERFACE_HANDLER_COUNT]; + char* path; + guint32 pending_signals; +}; + +typedef GObjectClass GSupplicantNetworkClass; +G_DEFINE_TYPE(GSupplicantNetwork, gsupplicant_network, G_TYPE_OBJECT) +#define GSUPPLICANT_NETWORK_TYPE (gsupplicant_network_get_type()) +#define GSUPPLICANT_NETWORK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GSUPPLICANT_NETWORK_TYPE, GSupplicantNetwork)) +#define SUPER_CLASS gsupplicant_network_parent_class + +/* Network properties */ +#define GSUPPLICANT_NETWORK_PROPERTIES_(p) \ + p(VALID,valid) \ + p(PRESENT,present) \ + p(PROPERTIES,properties) \ + p(ENABLED,enabled) + +typedef enum gsupplicant_network_signal { +#define SIGNAL_ENUM_(P,p) SIGNAL_##P##_CHANGED, + GSUPPLICANT_NETWORK_PROPERTIES_(SIGNAL_ENUM_) +#undef SIGNAL_ENUM_ + SIGNAL_PROPERTY_CHANGED, + SIGNAL_COUNT +} GSUPPLICANT_NETWORK_SIGNAL; + +#define SIGNAL_BIT(name) (1 << SIGNAL_##name##_CHANGED) + +/* + * The code assumes that VALID is the first one and that their number + * doesn't exceed the number of bits in pending_signals (currently, 32) + */ +G_STATIC_ASSERT(SIGNAL_VALID_CHANGED == 0); +G_STATIC_ASSERT(SIGNAL_PROPERTY_CHANGED <= 32); + +/* Assert that we have covered all publicly defined properties */ +G_STATIC_ASSERT((int)SIGNAL_PROPERTY_CHANGED == + ((int)GSUPPLICANT_NETWORK_PROPERTY_COUNT-1)); + +#define SIGNAL_PROPERTY_CHANGED_NAME "property-changed" +#define SIGNAL_PROPERTY_CHANGED_DETAIL "%x" +#define SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN (8) + +static GQuark gsupplicant_network_property_quarks[SIGNAL_PROPERTY_CHANGED]; +static guint gsupplicant_network_signals[SIGNAL_COUNT]; +static const char* gsupplicant_network_signame[] = { +#define SIGNAL_NAME_(P,p) #p "-changed", + GSUPPLICANT_NETWORK_PROPERTIES_(SIGNAL_NAME_) +#undef SIGNAL_NAME_ + SIGNAL_PROPERTY_CHANGED_NAME +}; + +G_STATIC_ASSERT(G_N_ELEMENTS(gsupplicant_network_signame) == SIGNAL_COUNT); + +/* Proxy properties */ +#define PROXY_PROPERTY_NAME_ENABLED "Enabled" +#define PROXY_PROPERTY_NAME_PROPERTIES "Properties" + +/* Weak references to the instances of GSupplicantNetwork */ +static GHashTable* gsupplicant_network_table = NULL; + +/*==========================================================================* + * Implementation + *==========================================================================*/ + +static inline +GSUPPLICANT_NETWORK_PROPERTY +gsupplicant_network_property_from_signal( + GSUPPLICANT_NETWORK_SIGNAL sig) +{ + switch (sig) { +#define SIGNAL_PROPERTY_MAP_(P,p) \ + case SIGNAL_##P##_CHANGED: return GSUPPLICANT_NETWORK_PROPERTY_##P; + GSUPPLICANT_NETWORK_PROPERTIES_(SIGNAL_PROPERTY_MAP_) +#undef SIGNAL_PROPERTY_MAP_ + default: /* unreachable */ return GSUPPLICANT_NETWORK_PROPERTY_ANY; + } +} + +static +void +gsupplicant_network_signal_property_change( + GSupplicantNetwork* self, + GSUPPLICANT_NETWORK_SIGNAL sig, + GSUPPLICANT_NETWORK_PROPERTY prop) +{ + GSupplicantNetworkPriv* priv = self->priv; + GASSERT(prop > GSUPPLICANT_NETWORK_PROPERTY_ANY); + GASSERT(prop < GSUPPLICANT_NETWORK_PROPERTY_COUNT); + GASSERT(sig < G_N_ELEMENTS(gsupplicant_network_property_quarks)); + if (!gsupplicant_network_property_quarks[sig]) { + char buf[SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN + 1]; + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_DETAIL, prop); + buf[sizeof(buf)-1] = 0; + gsupplicant_network_property_quarks[sig] = g_quark_from_string(buf); + } + priv->pending_signals &= ~(1 << sig); + g_signal_emit(self, gsupplicant_network_signals[sig], 0); + g_signal_emit(self, gsupplicant_network_signals[SIGNAL_PROPERTY_CHANGED], + gsupplicant_network_property_quarks[sig], prop); +} + +static +void +gsupplicant_network_emit_pending_signals( + GSupplicantNetwork* self) +{ + GSupplicantNetworkPriv* priv = self->priv; + GSUPPLICANT_NETWORK_SIGNAL sig; + gboolean valid_changed; + + /* Handlers could drops their references to us */ + gsupplicant_network_ref(self); + + /* VALID is the last one to be emitted if we BECOME valid */ + if ((priv->pending_signals & SIGNAL_BIT(VALID)) && self->valid) { + priv->pending_signals &= ~SIGNAL_BIT(VALID); + valid_changed = TRUE; + } else { + valid_changed = FALSE; + } + + /* Emit the signals. Not that in case if valid has become FALSE, then + * VALID is emitted first, otherwise it's emitted last */ + for (sig = SIGNAL_VALID_CHANGED; + sig < SIGNAL_COUNT && priv->pending_signals; + sig++) { + if (priv->pending_signals & (1 << sig)) { + gsupplicant_network_signal_property_change(self, sig, + gsupplicant_network_property_from_signal(sig)); + } + } + + /* Then emit VALID if valid has become TRUE */ + if (valid_changed) { + gsupplicant_network_signal_property_change(self, SIGNAL_VALID_CHANGED, + GSUPPLICANT_NETWORK_PROPERTY_VALID); + } + + /* And release the temporary reference */ + gsupplicant_network_unref(self); +} + +static +void +gsupplicant_network_update_valid( + GSupplicantNetwork* self) +{ + GSupplicantNetworkPriv* priv = self->priv; + const gboolean valid = priv->proxy && self->iface->valid; + if (self->valid != valid) { + self->valid = valid; + GDEBUG("Network %s is %svalid", priv->path, valid ? "" : "in"); + priv->pending_signals |= SIGNAL_BIT(VALID); + } +} + +static +void +gsupplicant_network_update_present( + GSupplicantNetwork* self) +{ + GSupplicantNetworkPriv* priv = self->priv; + const gboolean present = priv->proxy && self->iface->valid && + gutil_strv_contains(self->iface->networks, priv->path); + if (self->present != present) { + self->present = present; + GDEBUG("Network %s is %spresent", priv->path, present ? "" : "not "); + priv->pending_signals |= SIGNAL_BIT(PRESENT); + } +} + +static +GHashTable* +gsupplicant_network_get_properties( + GSupplicantNetwork* self) +{ + GHashTable* props = NULL; + GSupplicantNetworkPriv* priv = self->priv; + GVariant* dict = fi_w1_wpa_supplicant1_network_get_properties(priv->proxy); + if (dict) { + GVariantIter it; + GVariant* entry; + if (g_variant_is_of_type(dict, G_VARIANT_TYPE_VARIANT)) { + GVariant* tmp = g_variant_get_variant(dict); + g_variant_unref(dict); + dict = tmp; + } + props = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + for (g_variant_iter_init(&it, dict); + (entry = g_variant_iter_next_value(&it)) != NULL; + g_variant_unref(entry)) { + const guint num = g_variant_n_children(entry); + if (G_LIKELY(num == 2)) { + GVariant* key = g_variant_get_child_value(entry, 0); + GVariant* value = g_variant_get_child_value(entry, 1); + if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT)) { + GVariant* tmp = g_variant_get_variant(value); + g_variant_unref(value); + value = tmp; + } + if (g_variant_is_of_type(key, G_VARIANT_TYPE_STRING) && + g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + g_hash_table_replace(props, + g_strdup(g_variant_get_string(key, NULL)), + g_strdup(g_variant_get_string(value, NULL))); + } + g_variant_unref(key); + g_variant_unref(value); + } + } + } + return props; +} + +static +gboolean +gsupplicant_network_properties_equal( + GHashTable* p1, + GHashTable* p2) +{ + if (p1 && p2) { + const gsize size = g_hash_table_size(p1); + if (g_hash_table_size(p2) == size) { + if (size) { + gboolean equal = TRUE; + char** keys = (char**)g_hash_table_get_keys_as_array(p1, NULL); + const GStrV* ptr; + for (ptr = keys; *ptr; ptr++) { + const char* key = *ptr; + const char* value1 = g_hash_table_lookup(p1, key); + const char* value2 = g_hash_table_lookup(p2, key); + if (g_strcmp0(value1, value2)) { + equal = FALSE; + break; + } + } + g_free(keys); + return equal; + } + return TRUE; + } + return FALSE; + } else { + return !p1 && !p2; + } +} + +static +void +gsupplicant_network_update_properties( + GSupplicantNetwork* self) +{ + GSupplicantNetworkPriv* priv = self->priv; + GHashTable* props = gsupplicant_network_get_properties(self); + if (!gsupplicant_network_properties_equal(props, self->properties)) { + if (self->properties) { + g_hash_table_unref(self->properties); + } + self->properties = props; + priv->pending_signals |= SIGNAL_BIT(PROPERTIES); +#if GUTIL_LOG_VERBOSE + if (GLOG_ENABLED(GUTIL_LOG_VERBOSE)) { + if (props) { + char** keys; + const GStrV* ptr; + GVERBOSE("[%s] Properties:", self->path); + keys = (char**)g_hash_table_get_keys_as_array(props, NULL); + gutil_strv_sort(keys, TRUE); + for (ptr = keys; *ptr; ptr++) { + const char* key = *ptr; + const char* value = g_hash_table_lookup(props, key); + GVERBOSE(" %s: %s", key, value); + } + g_free(keys); + } else { + GVERBOSE("[%s] Properties: (null)", self->path); + } + } +#endif + } else if (props) { + g_hash_table_unref(props); + } +} + +static +void +gsupplicant_network_update_enabled( + GSupplicantNetwork* self) +{ + GSupplicantNetworkPriv* priv = self->priv; + gboolean b = fi_w1_wpa_supplicant1_network_get_enabled(priv->proxy); + if (self->enabled != b) { + self->enabled = b; + priv->pending_signals |= SIGNAL_BIT(ENABLED); + GVERBOSE("[%s] %s: %s", self->path, PROXY_PROPERTY_NAME_ENABLED, + b ? "true" : "false"); + } +} + +static +void +gsupplicant_network_proxy_gproperties_changed( + GDBusProxy* proxy, + GVariant* changed, + GStrv invalidated, + gpointer data) +{ + GSupplicantNetwork* self = GSUPPLICANT_NETWORK(data); + GSupplicantNetworkPriv* priv = self->priv; + if (invalidated) { + char** ptr; + for (ptr = invalidated; *ptr; ptr++) { + const char* name = *ptr; + if (!strcmp(name, PROXY_PROPERTY_NAME_PROPERTIES)) { + if (self->properties) { + g_hash_table_unref(self->properties); + self->properties = NULL; + priv->pending_signals |= SIGNAL_BIT(PROPERTIES); + } + } else if (!strcmp(name, PROXY_PROPERTY_NAME_ENABLED)) { + if (self->enabled) { + self->enabled = FALSE; + priv->pending_signals |= SIGNAL_BIT(ENABLED); + } + } + } + } + if (changed) { + GVariantIter it; + GVariant* value; + const char* name; + g_variant_iter_init(&it, changed); + while (g_variant_iter_next(&it, "{&sv}", &name, &value)) { + if (!strcmp(name, PROXY_PROPERTY_NAME_PROPERTIES)) { + gsupplicant_network_update_properties(self); + } else if (!strcmp(name, PROXY_PROPERTY_NAME_ENABLED)) { + gsupplicant_network_update_enabled(self); + } + g_variant_unref(value); + } + } + gsupplicant_network_emit_pending_signals(self); +} + +static +void +gsupplicant_network_proxy_properties_changed( + GDBusProxy* proxy, + GVariant* change, + gpointer data) +{ + gsupplicant_network_proxy_gproperties_changed(proxy, change, NULL, data); +} + +static +void +gsupplicant_network_interface_valid_changed( + GSupplicantInterface* iface, + void* data) +{ + GSupplicantNetwork* self = GSUPPLICANT_NETWORK(data); + GASSERT(self->iface == iface); + gsupplicant_network_update_valid(self); + gsupplicant_network_update_present(self); + gsupplicant_network_emit_pending_signals(self); +} + +static +void +gsupplicant_network_interface_networks_changed( + GSupplicantInterface* iface, + void* data) +{ + GSupplicantNetwork* self = GSUPPLICANT_NETWORK(data); + GASSERT(self->iface == iface); + gsupplicant_network_update_present(self); + gsupplicant_network_emit_pending_signals(self); +} + +static +void +gsupplicant_network_proxy_created( + GObject* bus, + GAsyncResult* res, + gpointer data) +{ + GSupplicantNetwork* self = GSUPPLICANT_NETWORK(data); + GSupplicantNetworkPriv* priv = self->priv; + GError* error = NULL; + GASSERT(!self->valid); + GASSERT(!priv->proxy); + priv->proxy = fi_w1_wpa_supplicant1_network_proxy_new_for_bus_finish(res, + &error); + if (priv->proxy) { + priv->proxy_handler_id[PROXY_GPROPERTIES_CHANGED] = + g_signal_connect(priv->proxy, "g-properties-changed", + G_CALLBACK(gsupplicant_network_proxy_gproperties_changed), self); + priv->proxy_handler_id[PROXY_PROPERTIES_CHANGED] = + g_signal_connect(priv->proxy, "properties-changed", + G_CALLBACK(gsupplicant_network_proxy_properties_changed), self); + + priv->iface_handler_id[INTERFACE_VALID_CHANGED] = + gsupplicant_interface_add_handler(self->iface, + GSUPPLICANT_INTERFACE_PROPERTY_VALID, + gsupplicant_network_interface_valid_changed, self); + priv->iface_handler_id[INTERFACE_NETWORKS_CHANGED] = + gsupplicant_interface_add_handler(self->iface, + GSUPPLICANT_INTERFACE_PROPERTY_NETWORKS, + gsupplicant_network_interface_networks_changed, self); + + gsupplicant_network_update_valid(self); + gsupplicant_network_update_present(self); + gsupplicant_network_update_properties(self); + gsupplicant_network_update_enabled(self); + + gsupplicant_network_emit_pending_signals(self); + } else { + GERR("%s", GERRMSG(error)); + g_error_free(error); + } + gsupplicant_network_unref(self); +} + +static +void +gsupplicant_network_destroyed( + gpointer key, + GObject* dead) +{ + GVERBOSE_("%s", (char*)key); + GASSERT(gsupplicant_network_table); + if (gsupplicant_network_table) { + GASSERT(g_hash_table_lookup(gsupplicant_network_table, key) == dead); + g_hash_table_remove(gsupplicant_network_table, key); + if (g_hash_table_size(gsupplicant_network_table) == 0) { + g_hash_table_unref(gsupplicant_network_table); + gsupplicant_network_table = NULL; + } + } +} + +static +GSupplicantNetwork* +gsupplicant_network_create( + const char* path) +{ + /* + * Let's assume that the network path has the following format: + * + * /fi/w1/wpa_supplicant1/Interfaces/xxx/Networks/yyy + * + * and we just have to strip the last two elements of the path to get + * the interface path. + */ + int slash_count = 0; + const char* ptr; + for (ptr = path + strlen(path); ptr > path; ptr--) { + if (ptr[0] == '/') { + slash_count++; + if (slash_count == 2) { + break; + } + } + } + if (ptr > path) { + GSupplicantInterface* iface; + /* Temporarily shorten the path to lookup the interface */ + char* path2 = g_strdup(path); + const gsize slash_index = ptr - path; + path2[slash_index] = 0; + GDEBUG_("%s -> %s", path, path2); + iface = gsupplicant_interface_new(path2); + if (iface) { + GSupplicantNetwork* self = + g_object_new(GSUPPLICANT_NETWORK_TYPE,NULL); + GSupplicantNetworkPriv* priv = self->priv; + /* Path is already allocated (but truncated) */ + path2[slash_index] = '/'; + self->path = priv->path = path2; + self->iface = iface; + fi_w1_wpa_supplicant1_network_proxy_new_for_bus( + GSUPPLICANT_BUS_TYPE, G_DBUS_PROXY_FLAGS_NONE, + GSUPPLICANT_SERVICE, self->path, NULL, + gsupplicant_network_proxy_created, + gsupplicant_network_ref(self)); + return self; + } + g_free(path2); + } + return NULL; +} + +/*==========================================================================* + * API + *==========================================================================*/ + +GSupplicantNetwork* +gsupplicant_network_new( + const char* path) +{ + GSupplicantNetwork* self = NULL; + if (G_LIKELY(path)) { + self = gsupplicant_network_table ? gsupplicant_network_ref( + g_hash_table_lookup(gsupplicant_network_table, path)) : NULL; + if (!self) { + self = gsupplicant_network_create(path); + if (self) { + gpointer key = g_strdup(path); + if (!gsupplicant_network_table) { + gsupplicant_network_table = + g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + } + g_hash_table_replace(gsupplicant_network_table, key, self); + g_object_weak_ref(G_OBJECT(self), + gsupplicant_network_destroyed, key); + } + } + } + return self; +} + +GSupplicantNetwork* +gsupplicant_network_ref( + GSupplicantNetwork* self) +{ + if (G_LIKELY(self)) { + g_object_ref(GSUPPLICANT_NETWORK(self)); + return self; + } else { + return NULL; + } +} + +void +gsupplicant_network_unref( + GSupplicantNetwork* self) +{ + if (G_LIKELY(self)) { + g_object_unref(GSUPPLICANT_NETWORK(self)); + } +} + +gulong +gsupplicant_network_add_property_changed_handler( + GSupplicantNetwork* self, + GSUPPLICANT_NETWORK_PROPERTY property, + GSupplicantNetworkPropertyFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signal_name; + char buf[sizeof(SIGNAL_PROPERTY_CHANGED_NAME) + 2 + + SIGNAL_PROPERTY_CHANGED_DETAIL_MAX_LEN]; + if (property) { + snprintf(buf, sizeof(buf), SIGNAL_PROPERTY_CHANGED_NAME "::" + SIGNAL_PROPERTY_CHANGED_DETAIL, property); + buf[sizeof(buf)-1] = 0; + signal_name = buf; + } else { + signal_name = SIGNAL_PROPERTY_CHANGED_NAME; + } + return g_signal_connect(self, signal_name, G_CALLBACK(fn), data); + } + return 0; +} + +gulong +gsupplicant_network_add_handler( + GSupplicantNetwork* self, + GSUPPLICANT_NETWORK_PROPERTY prop, + GSupplicantNetworkFunc fn, + void* data) +{ + if (G_LIKELY(self) && G_LIKELY(fn)) { + const char* signame; + switch (prop) { +#define SIGNAL_NAME_(P,p) case GSUPPLICANT_NETWORK_PROPERTY_##P: \ + signame = gsupplicant_network_signame[SIGNAL_##P##_CHANGED]; \ + break; + GSUPPLICANT_NETWORK_PROPERTIES_(SIGNAL_NAME_) + default: + signame = NULL; + break; + } + if (G_LIKELY(signame)) { + return g_signal_connect(self, signame, G_CALLBACK(fn), data); + } + } + return 0; +} + +void +gsupplicant_network_remove_handler( + GSupplicantNetwork* self, + gulong id) +{ + if (G_LIKELY(self) && G_LIKELY(id)) { + g_signal_handler_disconnect(self, id); + } +} + +void +gsupplicant_network_remove_handlers( + GSupplicantNetwork* self, + gulong* ids, + guint count) +{ + gutil_disconnect_handlers(self, ids, count); +} + +gboolean +gsupplicant_network_set_enabled( + GSupplicantNetwork* self, + gboolean enabled) +{ + if (G_LIKELY(self) && self->valid) { + GSupplicantNetworkPriv* priv = self->priv; + fi_w1_wpa_supplicant1_network_set_enabled(priv->proxy, enabled); + return TRUE; + } + return FALSE; +} + +/*==========================================================================* + * Internals + *==========================================================================*/ + +/** + * Per instance initializer + */ +static +void +gsupplicant_network_init( + GSupplicantNetwork* self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, GSUPPLICANT_NETWORK_TYPE, + GSupplicantNetworkPriv); +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +gsupplicant_network_dispose( + GObject* object) +{ + GSupplicantNetwork* self = GSUPPLICANT_NETWORK(object); + GSupplicantNetworkPriv* priv = self->priv; + if (priv->proxy) { + gutil_disconnect_handlers(priv->proxy, priv->proxy_handler_id, + G_N_ELEMENTS(priv->proxy_handler_id)); + g_object_unref(priv->proxy); + priv->proxy = NULL; + } + gsupplicant_interface_remove_handlers(self->iface, priv->iface_handler_id, + G_N_ELEMENTS(priv->iface_handler_id)); + G_OBJECT_CLASS(SUPER_CLASS)->dispose(object); +} + +/** + * Final stage of deinitialization + */ +static +void +gsupplicant_network_finalize( + GObject* object) +{ + GSupplicantNetwork* self = GSUPPLICANT_NETWORK(object); + GSupplicantNetworkPriv* priv = self->priv; + GASSERT(!priv->proxy); + g_free(priv->path); + gsupplicant_interface_unref(self->iface); + if (self->properties) { + g_hash_table_unref(self->properties); + } + G_OBJECT_CLASS(SUPER_CLASS)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +gsupplicant_network_class_init( + GSupplicantNetworkClass* klass) +{ + int i; + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = gsupplicant_network_dispose; + object_class->finalize = gsupplicant_network_finalize; + g_type_class_add_private(klass, sizeof(GSupplicantNetworkPriv)); + for (i=0; i + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant_util_p.h" +#include "gsupplicant_log.h" + +#include + +const char* +gsupplicant_name_int_find_bit( + guint value, + guint* bit, + const GSupNameIntPair* list, + gsize count) +{ + gsize i; + for (i=0; ivalue : default_value; +} + +static +const GSupNameIntPair* +gsupplicant_name_int_find_name_impl( + const char* name, + int (*cmp)(const char* s1, const char* s2), + const GSupNameIntPair* list, + gsize count) +{ + if (name) { + gsize i; + for (i=0; ivalue; + return TRUE; + } else { + return FALSE; + } +} + +char* +gsupplicant_name_int_concat( + guint value, + char separator, + const GSupNameIntPair* list, + gsize count) +{ + GString* buf = NULL; + gsize i; + for (i=0; ilen > 0) { + g_string_append_c(buf, separator); + } + g_string_append(buf, list[i].name); + } + } + return buf ? g_string_free(buf, FALSE) : NULL; +} + +guint32 +gsupplicant_parse_bits_array( + guint32 mask, + const char* name, + GVariant* value, + const GSupNameIntPair* map, + gsize count) +{ + if (g_variant_is_of_type(value, G_VARIANT_TYPE("as"))) { + GVariantIter it; + char* str = NULL; +#if GUTIL_LOG_VERBOSE + GString* buf = GLOG_ENABLED(GUTIL_LOG_VERBOSE) ? + g_string_new(NULL) : NULL; +#endif + g_variant_iter_init(&it, value); + while (g_variant_iter_loop(&it, "s", &str)) { + if (gsupplicant_name_int_set_bits(&mask, str, map, count)) { +#if GUTIL_LOG_VERBOSE + if (buf) { + if (buf->len) g_string_append_c(buf, ','); + g_string_append(buf, str); + } +#endif + } else { + GWARN("Unexpected %s value %s", name, str); + } + } +#if GUTIL_LOG_VERBOSE + if (buf) { + GVERBOSE(" %s: %s", name, buf->str); + g_string_free(buf, TRUE); + } +#endif + } else { + GWARN("Unexpected value type for %s", name); + } + return mask; +} + +void* +gsupplicant_hex2bin( + const char* str, + gint len, + guint8* data) +{ + if (str) { + if (len < 0) len = strlen(str); + if (len > 0 && !(len & 1)) { + gsize i; + guint8* ptr = data; + for (i=0; i 0) g_string_append_c(buf, ':'); + g_string_append_printf(buf, "%02x", data[i]); + } + if (append_length) { + g_string_append_printf(buf, " (%u)", (guint)size); + } + str = g_string_free(buf, FALSE); + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, gsupplicant_idle_cb, + str, g_free); + return str; + } else { + return "(null)"; + } +} + +static +gboolean +gsupplicant_dummy_source_func( + gpointer cancel) +{ + return G_SOURCE_REMOVE; +} + +guint +gsupplicant_call_later( + GDestroyNotify notify, + void* data) +{ + return g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + gsupplicant_dummy_source_func, data, notify); +} + +static +gboolean +gsupplicant_cancel_later_cb( + gpointer cancel) +{ + g_cancellable_cancel(cancel); + return G_SOURCE_REMOVE; +} + +guint +gsupplicant_cancel_later( + GCancellable* cancel) +{ + if (cancel) { + return g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + gsupplicant_cancel_later_cb, g_object_ref(cancel), + g_object_unref); + } + return 0; +} + +const char* +gsupplicant_check_abs_path( + const char* path) +{ + if (path && path[0]) { + if (!g_path_is_absolute(path)) { + GWARN("Not an absolute path: %s", path); + return NULL; + } else if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) { + GWARN("No such file: %s", path); + return NULL; + } else { + return path; + } + } + return NULL; +} + +int +gsupplicant_dict_parse( + GVariant* dict, + GSupplicantDictStrFunc fn, + void* data) +{ + int count = 0; + if (dict) { + GVariantIter it; + GVariant* entry; + for (g_variant_iter_init(&it, dict); + (entry = g_variant_iter_next_value(&it)) != NULL; + g_variant_unref(entry)) { + const guint num = g_variant_n_children(entry); + if (G_LIKELY(num == 2)) { + GVariant* key = g_variant_get_child_value(entry, 0); + GVariant* value = g_variant_get_child_value(entry, 1); + if (g_variant_is_of_type(value, G_VARIANT_TYPE_VARIANT)) { + GVariant* tmp = g_variant_get_variant(value); + g_variant_unref(value); + value = tmp; + } + if (g_variant_is_of_type(key, G_VARIANT_TYPE_STRING)) { + fn(g_variant_get_string(key, NULL), value, data); + count++; + } + g_variant_unref(key); + g_variant_unref(value); + } + } + } + return count; +} + +void +gsupplicant_dict_add_value( + GVariantBuilder* builder, + const char* name, + GVariant* value) +{ + g_variant_builder_add(builder, "{sv}", name, value); +} + +void +gsupplicant_dict_add_boolean( + GVariantBuilder* builder, + const char* name, + gboolean value) +{ + gsupplicant_dict_add_value(builder, name, g_variant_new_boolean(value)); +} + +void +gsupplicant_dict_add_uint32( + GVariantBuilder* builder, + const char* name, + guint32 value) +{ + gsupplicant_dict_add_value(builder, name, g_variant_new_uint32(value)); +} + +void +gsupplicant_dict_add_string( + GVariantBuilder* builder, + const char* name, + const char* value) +{ + gsupplicant_dict_add_value(builder, name, g_variant_new_string(value)); +} + +void +gsupplicant_dict_add_string0( + GVariantBuilder* builder, + const char* name, + const char* value) +{ + if (name && value) { + gsupplicant_dict_add_string(builder, name, value); + } +} + +void +gsupplicant_dict_add_string_ne( + GVariantBuilder* builder, + const char* name, + const char* value) +{ + if (name && value && value[0]) { + gsupplicant_dict_add_string(builder, name, value); + } +} + +void +gsupplicant_dict_add_bytes( + GVariantBuilder* builder, + const char* name, + GBytes* value) +{ + gsize size = 0; + const void* data = g_bytes_get_data(value, &size); + gsupplicant_dict_add_value(builder, name, g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, data, size, 1)); +} + +void +gsupplicant_dict_add_bytes0( + GVariantBuilder* builder, + const char* name, + GBytes* value) +{ + if (name && value) { + gsupplicant_dict_add_bytes(builder, name, value); + } +} + +GVariant* +gsupplicant_variant_new_ayy( + GBytes** bytes) +{ + GVariantBuilder ayy; + g_variant_builder_init(&ayy, G_VARIANT_TYPE("aay")); + if (bytes) { + while (*bytes) { + gsize size = 0; + const void* data = g_bytes_get_data(*bytes, &size); + g_variant_builder_add_value(&ayy, g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, data, size, 1)); + bytes++; + } + } + return g_variant_builder_end(&ayy); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/src/gsupplicant_util_p.h b/src/gsupplicant_util_p.h new file mode 100644 index 0000000..6293e0f --- /dev/null +++ b/src/gsupplicant_util_p.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GSUPPLICANT_UTIL_PRIVATE_H +#define GSUPPLICANT_UTIL_PRIVATE_H + +#include + +typedef struct gsupplicant_name_int_pair { + const char* name; + guint value; +} GSupNameIntPair; + +typedef +void +(*GSupplicantDictStrFunc)( + const char* key, + GVariant* value, + void* data); + +guint32 +gsupplicant_parse_bits_array( + guint32 mask, + const char* name, + GVariant* value, + const GSupNameIntPair* map, + gsize count); + +const char* +gsupplicant_name_int_find_bit( + guint value, + guint* bit, + const GSupNameIntPair* list, + gsize count); + +const char* +gsupplicant_name_int_find_int( + guint value, + const GSupNameIntPair* list, + gsize count); + +guint +gsupplicant_name_int_get_int( + const char* name, + const GSupNameIntPair* list, + gsize count, + guint default_value); + +gboolean +gsupplicant_name_int_set_bits( + guint* bitmask, + const char* name, + const GSupNameIntPair* list, + gsize count); + +const GSupNameIntPair* +gsupplicant_name_int_find_name( + const char* name, + const GSupNameIntPair* list, + gsize count); + +const GSupNameIntPair* +gsupplicant_name_int_find_name_i( + const char* name, + const GSupNameIntPair* list, + gsize count); + +char* +gsupplicant_name_int_concat( + guint value, + char separator, + const GSupNameIntPair* list, + gsize count); + +void* +gsupplicant_hex2bin( + const char* str, + gint len, + guint8* data); + +const char* +gsupplicant_format_bytes( + GBytes* bytes, + gboolean append_length); + +guint +gsupplicant_call_later( + GDestroyNotify notify, + void* data); + +guint +gsupplicant_cancel_later( + GCancellable* cancel); + +const char* +gsupplicant_check_abs_path( + const char* path); + +int +gsupplicant_dict_parse( + GVariant* dict, + GSupplicantDictStrFunc fn, + void* data); + +void +gsupplicant_dict_add_value( + GVariantBuilder* builder, + const char* name, + GVariant* value); + +void +gsupplicant_dict_add_boolean( + GVariantBuilder* builder, + const char* name, + gboolean value); + +void +gsupplicant_dict_add_uint32( + GVariantBuilder* builder, + const char* name, + guint32 value); + +void +gsupplicant_dict_add_string( + GVariantBuilder* builder, + const char* name, + const char* value); + +void +gsupplicant_dict_add_string0( + GVariantBuilder* builder, + const char* name, + const char* value); + +void +gsupplicant_dict_add_string_ne( + GVariantBuilder* builder, + const char* name, + const char* value); + +void +gsupplicant_dict_add_bytes( + GVariantBuilder* builder, + const char* name, + GBytes* value); + +void +gsupplicant_dict_add_bytes0( + GVariantBuilder* builder, + const char* name, + GBytes* value); + +GVariant* +gsupplicant_variant_new_ayy( + GBytes** bytes); + +#endif /* GSUPPLICANT_UTIL_PRIVATE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/tools/wpa-tool/Makefile b/tools/wpa-tool/Makefile new file mode 100644 index 0000000..efcde1b --- /dev/null +++ b/tools/wpa-tool/Makefile @@ -0,0 +1,139 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: clean all debug release libgsupplicant-release libgsupplicant-debug + +# +# Required packages +# + +PKGS = glib-2.0 gio-2.0 gio-unix-2.0 libglibutil + +# +# Default target +# + +all: debug release + +# +# Executable +# + +EXE = wpa-tool + +# +# Sources +# + +SRC = $(EXE).c + +# +# Directories +# + +SRC_DIR = . +BUILD_DIR = build +LIB_DIR = ../.. +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +WARNINGS = -Wall +INCLUDES = -I$(LIB_DIR)/include +BASE_FLAGS = -fPIC +CFLAGS = $(BASE_FLAGS) $(DEFINES) $(WARNINGS) $(INCLUDES) -MMD -MP \ + $(shell pkg-config --cflags $(PKGS)) +LDFLAGS = $(BASE_FLAGS) $(shell pkg-config --libs $(PKGS)) +QUIET_MAKE = make --no-print-directory +DEBUG_FLAGS = -g +RELEASE_FLAGS = + +ifndef KEEP_SYMBOLS +KEEP_SYMBOLS = 0 +endif + +ifneq ($(KEEP_SYMBOLS),0) +RELEASE_FLAGS += -g +SUBMAKE_OPTS += KEEP_SYMBOLS=1 +endif + +DEBUG_LDFLAGS = $(LDFLAGS) $(DEBUG_FLAGS) +RELEASE_LDFLAGS = $(LDFLAGS) $(RELEASE_FLAGS) +DEBUG_CFLAGS = $(CFLAGS) $(DEBUG_FLAGS) -DDEBUG +RELEASE_CFLAGS = $(CFLAGS) $(RELEASE_FLAGS) -O2 + +# +# Files +# + +DEBUG_OBJS = $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) +DEBUG_LIB_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_debug_lib) +RELEASE_LIB_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_release_lib) +DEBUG_LINK_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_debug_link) +RELEASE_LINK_FILE := $(shell $(QUIET_MAKE) -C $(LIB_DIR) print_release_link) +DEBUG_LIB = $(LIB_DIR)/$(DEBUG_LIB_FILE) +RELEASE_LIB = $(LIB_DIR)/$(RELEASE_LIB_FILE) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +$(DEBUG_OBJS): | $(DEBUG_BUILD_DIR) +$(RELEASE_OBJS): | $(RELEASE_BUILD_DIR) + +# +# Rules +# + +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: libgsupplicant-debug $(DEBUG_EXE) + +release: libgsupplicant-release $(RELEASE_EXE) + +clean: + rm -f *~ + rm -fr $(BUILD_DIR) + +cleaner: clean + @make -C $(LIB_DIR) clean + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_LIB) $(DEBUG_BUILD_DIR) $(DEBUG_OBJS) + $(LD) $(DEBUG_OBJS) $(DEBUG_LDFLAGS) $< -o $@ + +$(RELEASE_EXE): $(RELEASE_LIB) $(RELEASE_BUILD_DIR) $(RELEASE_OBJS) + $(LD) $(RELEASE_OBJS) $(RELEASE_LDFLAGS) $< -o $@ +ifeq ($(KEEP_SYMBOLS),0) + strip $@ +endif + +libgsupplicant-debug: + @make $(SUBMAKE_OPTS) -C $(LIB_DIR) $(DEBUG_LIB_FILE) $(DEBUG_LINK_FILE) + +libgsupplicant-release: + @make $(SUBMAKE_OPTS) -C $(LIB_DIR) $(RELEASE_LIB_FILE) $(RELEASE_LINK_FILE) diff --git a/tools/wpa-tool/wpa-tool.c b/tools/wpa-tool/wpa-tool.c new file mode 100644 index 0000000..67f8d0e --- /dev/null +++ b/tools/wpa-tool/wpa-tool.c @@ -0,0 +1,1294 @@ +/* + * Copyright (C) 2015-2017 Jolla Ltd. + * Contact: Slava Monich + * + * You may use this file under the terms of BSD license as follows: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Jolla Ltd nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gsupplicant.h" +#include "gsupplicant_bss.h" +#include "gsupplicant_network.h" +#include "gsupplicant_interface.h" + +#include +#include +#include + +#include + +#define RET_OK (0) +#define RET_NOTFOUND (1) +#define RET_ERR (2) +#define RET_TIMEOUT (3) + +typedef struct app_action AppAction; +typedef GCancellable* (*AppActionRunFunc)(AppAction* action); +typedef void (*AppActionFreeFunc)(AppAction* action); + +typedef struct app { + gint timeout; + GMainLoop* loop; + GSupplicant* supplicant; + GCancellable* call; + AppAction* actions; + gboolean dump_properties; + gboolean list_interfaces; + gboolean list_caps; + gboolean list_eap_methods; + gboolean follow_properties; + gboolean pick_interface; + gulong supplicant_valid_signal_id; + char* iface_path; + GSupplicantInterface* iface; + gulong iface_valid_signal_id; + gulong iface_prop_signal_id; + char* bss_path; + GSupplicantBSS* bss; + gulong bss_valid_signal_id; + gulong bss_prop_signal_id; + char* network_path; + GSupplicantNetwork* network; + gulong network_valid_signal_id; + gulong network_prop_signal_id; + int ret; +} App; + +struct app_action { + AppAction* next; + App* app; + AppActionRunFunc fn_run; + AppActionFreeFunc fn_free; +}; + +typedef struct app_action_str { + AppAction action; + char* str; +} AppActionStr; + +typedef struct app_action_int { + AppAction action; + int value; +} AppActionInt; + +#define dump_supplicant_caps(bits,d1,d2) \ + dump_bits(bits, gsupplicant_caps_name, d1, d2) +#define dump_supplicant_eap_methods(bits,d1,d2) \ + dump_bits(bits, gsupplicant_eap_method_name, d1, d2) +#define dump_cipher_suites(bits,d1,d2) \ + dump_bits(bits, gsupplicant_cipher_suite_name, d1, d2) +#define dump_keymgmt_suites(bits,d1,d2) \ + dump_bits(bits, gsupplicant_keymgmt_suite_name, d1, d2) + +static +void +app_follow( + App* app); + +static +void +app_quit( + App* app) +{ + g_idle_add((GSourceFunc)g_main_loop_quit, app->loop); +} + +static +void +app_next_action( + App* app) +{ + while (app->actions && !app->call) { + AppAction* action = app->actions; + app->actions = action->next; + action->next = NULL; + app->call = action->fn_run(action); + if (!app->call) { + action->fn_free(action); + } + } + if (!app->call) { + if (app->follow_properties) { + app_follow(app); + } else { + app_quit(app); + } + } +} + +static +void +app_action_call_done( + AppAction* action, + const GError* error) +{ + App* app = action->app; + app->call = NULL; + action->fn_free(action); + if (error) { + GERR("%s", error->message); + app->ret = RET_ERR; + app_quit(app); + } else { + app_next_action(app); + } +} + +static +void +app_add_action( + App* app, + AppAction* action) +{ + if (app->actions) { + AppAction* last = app->actions; + while (last->next) { + last = last->next; + } + last->next = action; + } else { + app->actions = action; + } +} + +static +void +app_action_free( + AppAction* action) +{ + g_free(action); +} + +static +void +app_action_str_free( + AppAction* action) +{ + AppActionStr* str_action = G_CAST(action, AppActionStr, action); + g_free(str_action->str); + g_free(str_action); +} + +static +AppAction* +app_action_new( + App* app, + AppActionRunFunc run) +{ + AppAction* action = g_new0(AppAction, 1); + action->app = app; + action->fn_run = run; + action->fn_free = app_action_free; + return action; +} + +static +AppAction* +app_action_str_new( + App* app, + AppActionRunFunc run, + const char* str) +{ + AppActionStr* str_action = g_new0(AppActionStr, 1); + AppAction* action = &str_action->action; + action->app = app; + action->fn_run = run; + action->fn_free = app_action_str_free; + str_action->str = g_strdup(str); + return action; +} + +static +AppAction* +app_action_int_new( + App* app, + AppActionRunFunc run, + int value) +{ + AppActionInt* int_action = g_new0(AppActionInt, 1); + AppAction* action = &int_action->action; + action->app = app; + action->fn_run = run; + action->fn_free = app_action_free; + int_action->value = value; + return action; +} + +static +void +dump_strv( + const GStrV* strv, + const char* d1, + const char* d2) +{ + if (strv) { + const char* delimiter = d1; + while (*strv) { + printf("%s%s", delimiter, *strv); + delimiter = d2; + strv++; + } + } +} + +static +void +dump_bits( + guint bits, + const char* (*proc)(guint bits, guint* bit), + const char* d1, + const char* d2) +{ + guint bit; + const char* name; + const char* delimiter = d1; + while ((name = proc(bits, &bit)) != NULL) { + printf("%s%s", delimiter, name); + delimiter = d2; + bits &= ~bit; + } +} + +static +void +dump_bytes( + GBytes* bytes, + const char* d1, + const char* d2) +{ + if (bytes) { + gsize i, size = 0; + const guint8* data = g_bytes_get_data(bytes, &size); + const char* delimiter = d1; + for (i=0; i\n"); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_STATE: + printf("State: %s\n", gsupplicant_interface_state_name(iface->state)); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_SCANNING: + printf("Scanning: %s\n", iface->scanning ? "yes" : "no"); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_AP_SCAN: + printf("ApScan: %u\n", iface->ap_scan); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_COUNTRY: + printf("Country: %s\n", iface->country); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_DRIVER: + printf("Driver: %s\n", iface->driver); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_IFNAME: + printf("Ifname: %s\n", iface->ifname); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_BRIDGE_IFNAME: + printf("BridgeIfname: %s\n", iface->bridge_ifname); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_CURRENT_BSS: + printf("CurrentBSS: %s\n", iface->current_bss); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_CURRENT_NETWORK: + printf("CurrentNetwork: %s\n", iface->current_network); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_SCAN_INTERVAL: + printf("ScanInterval: %d\n", iface->scan_interval); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_BSSS: + printf("BSSs:"); + dump_strv(iface->bsss, " ", ","); + printf("\n"); + break; + case GSUPPLICANT_INTERFACE_PROPERTY_NETWORKS: + printf("Networks:"); + dump_strv(iface->networks, " ", ","); + printf("\n"); + break; + default: + break; + } +} + +static +void +dump_bss_property( + GSupplicantBSS* bss, + GSUPPLICANT_BSS_PROPERTY property) +{ + switch (property) { + case GSUPPLICANT_BSS_PROPERTY_SSID: + printf("SSID: %s\n", bss->ssid_str); + break; + case GSUPPLICANT_BSS_PROPERTY_BSSID: + dump_bytes(bss->bssid, "BSSID: ", ":"); + printf("\n"); + break; + case GSUPPLICANT_BSS_PROPERTY_WPA: + if (bss->wpa) { + const GSupplicantBSSWPA* wpa = bss->wpa; + printf("WPA:"); + if (wpa->keymgmt) { + printf(" KeyMgmt("); + dump_keymgmt_suites(wpa->keymgmt, "", ","); + printf(")"); + } + if (wpa->pairwise) { + printf(" Pairwise("); + dump_cipher_suites(wpa->pairwise, "", ","); + printf(")"); + } + if (wpa->group) { + printf(" Group("); + dump_cipher_suites(wpa->group, "", ","); + printf(")"); + } + printf("\n"); + } + break; + case GSUPPLICANT_BSS_PROPERTY_RSN: + if (bss->rsn) { + const GSupplicantBSSRSN* rsn = bss->rsn; + printf("RSN:"); + if (rsn->keymgmt) { + printf(" KeyMgmt("); + dump_keymgmt_suites(rsn->keymgmt, "", ","); + printf(")"); + } + if (rsn->pairwise) { + printf(" Pairwise("); + dump_cipher_suites(rsn->pairwise, "", ","); + printf(")"); + } + if (rsn->group) { + printf(" Group("); + dump_cipher_suites(rsn->group, "", ","); + printf(")"); + } + if (rsn->mgmt_group) { + printf(" MgmtGroup("); + dump_cipher_suites(rsn->mgmt_group, "", ","); + printf(")"); + } + printf("\n"); + } + break; + case GSUPPLICANT_BSS_PROPERTY_IES: + dump_bytes(bss->ies, "IEs: ", ":"); + printf("\n"); + break; + case GSUPPLICANT_BSS_PROPERTY_PRIVACY: + printf("Privacy: %s\n", bss->privacy ? "yes" : "no"); + break; + case GSUPPLICANT_BSS_PROPERTY_MODE: + printf("Mode: "); + switch (bss->mode) { + case GSUPPLICANT_BSS_MODE_INFRA: + printf("infrastructure"); + break; + case GSUPPLICANT_BSS_MODE_AD_HOC: + printf("ad-hoc"); + break; + default: + printf("%d", bss->mode); + break; + } + printf("\n"); + break; + case GSUPPLICANT_BSS_PROPERTY_FREQUENCY: + printf("Frequency: %u\n", bss->frequency); + break; + case GSUPPLICANT_BSS_PROPERTY_RATES: + if (bss->rates) { + guint i; + printf("Rates: ["); + for (i=0; irates->count; i++) { + if (i > 0) printf(","); + printf("%u", bss->rates->values[i]); + } + printf("]\n"); + } + break; + case GSUPPLICANT_BSS_PROPERTY_SIGNAL: + printf("Signal: %d\n", bss->signal); + break; + default: + break; + } +} + +static +void +dump_network_property( + GSupplicantNetwork* network, + GSUPPLICANT_NETWORK_PROPERTY property) +{ + switch (property) { + case GSUPPLICANT_NETWORK_PROPERTY_ENABLED: + printf("Enabled: %s\n", network->enabled ? "yes" : "no"); + break; + case GSUPPLICANT_NETWORK_PROPERTY_PROPERTIES: + if (network->properties) { + const GStrV* ptr; + GHashTable* p = network->properties; + if (p) { + char** keys = (char**)g_hash_table_get_keys_as_array(p, NULL); + printf("Properties: %d key(s)\n", g_hash_table_size(p)); + gutil_strv_sort(keys, TRUE); + for (ptr = keys; *ptr; ptr++) { + const char* key = *ptr; + const char* value = g_hash_table_lookup(p, key); + printf(" %s: %s\n", key, value); + } + g_free(keys); + } else { + printf("Properties: (null)\n"); + } + } + break; + default: + break; + } +} + +static +void +app_action_generic_result( + GSupplicant* supplicant, + GCancellable* cancel, + const GError* error, + void* data) +{ + app_action_call_done(data, error); +} + +static +void +app_action_generic_interface_result( + GSupplicantInterface* iface, + GCancellable* cancel, + const GError* error, + void* data) +{ + app_action_call_done(data, error); +} + +static +void +app_action_string_result( + GSupplicant* supplicant, + GCancellable* cancel, + const GError* error, + const char* str, + void* data) +{ + AppAction* action = data; + if (!error) { + printf("%s\n", str); + } + app_action_call_done(action, error); +} + +static +void +app_action_signal_poll_result( + GSupplicantInterface* iface, + GCancellable* cancel, + const GError* error, + const GSupplicantSignalPoll* info, + void* data) +{ + AppAction* action = data; + if (info) { + if (info->flags & GSUPPLICANT_SIGNAL_POLL_LINKSPEED) { + printf("linkspeed: %d\n", info->linkspeed); + } + if (info->flags & GSUPPLICANT_SIGNAL_POLL_NOISE) { + printf("noise: %d\n", info->noise); + } + if (info->flags & GSUPPLICANT_SIGNAL_POLL_FREQUENCY) { + printf("frequency: %u\n", info->frequency); + } + if (info->flags & GSUPPLICANT_SIGNAL_POLL_RSSI) { + printf("rssi: %d\n", info->rssi); + } + if (info->flags & GSUPPLICANT_SIGNAL_POLL_AVG_RSSI) { + printf("avg_rssi: %d\n", info->avg_rssi); + } + if (info->flags & GSUPPLICANT_SIGNAL_POLL_CENTER_FRQ1) { + printf("center_frq1: %d\n", info->center_frq1); + } + if (info->flags & GSUPPLICANT_SIGNAL_POLL_CENTER_FRQ2) { + printf("center_frq2: %d\n", info->center_frq2); + } + } + app_action_call_done(action, error); +} + +static +GCancellable* +app_action_signal_poll( + AppAction* action) +{ + App* app = action->app; + return gsupplicant_interface_signal_poll(app->iface, + app_action_signal_poll_result, action); +} + +static +GCancellable* +app_action_ap_scan( + AppAction* action) +{ + App* app = action->app; + AppActionInt* int_action = G_CAST(action,AppActionInt,action); + GDEBUG("Settings ap_scan to %d", int_action->value); + gsupplicant_interface_set_ap_scan(app->iface, int_action->value); + return NULL; +} + +static +GCancellable* +app_action_country( + AppAction* action) +{ + App* app = action->app; + AppActionStr* str_action = G_CAST(action,AppActionStr,action); + GDEBUG("Settings country to %s", str_action->str); + gsupplicant_interface_set_country(app->iface, str_action->str); + return NULL; +} + +static +GCancellable* +app_action_create_interface( + AppAction* action) +{ + App* app = action->app; + AppActionStr* sa = G_CAST(action,AppActionStr,action); + GSupplicantCreateInterfaceParams params; + memset(¶ms, 0, sizeof(params)); + params.ifname = sa->str; + return gsupplicant_create_interface(app->supplicant, ¶ms, + app_action_string_result, sa); +} + +static +GCancellable* +app_action_get_interface( + AppAction* action) +{ + App* app = action->app; + AppActionStr* sa = G_CAST(action,AppActionStr,action); + return gsupplicant_get_interface(app->supplicant, sa->str, + app_action_string_result, sa); +} + +static +GCancellable* +app_action_remove_interface( + AppAction* action) +{ + App* app = action->app; + AppActionStr* sa = G_CAST(action,AppActionStr,action); + return gsupplicant_remove_interface(app->supplicant, sa->str, + app_action_generic_result, action); +} + +static +GCancellable* +app_action_passive_scan( + AppAction* action) +{ + App* app = action->app; + GDEBUG("Doing passive scan"); + return gsupplicant_interface_scan(app->iface, NULL, + app_action_generic_interface_result, action); +} + +static +GCancellable* +app_action_active_scan( + AppAction* action) +{ + App* app = action->app; + AppActionStr* str_action = G_CAST(action,AppActionStr,action); + GSupplicantScanParams params; + GCancellable* call; + GBytes* ssid = g_bytes_new(str_action->str, strlen(str_action->str)); + GBytes* ssids[2]; + ssids[0] = ssid; + ssids[1] = NULL; + GDEBUG("Doing active scan for %s", str_action->str); + memset(¶ms, 0, sizeof(params)); + params.type = GSUPPLICANT_SCAN_TYPE_ACTIVE; + params.ssids = ssids; + call = gsupplicant_interface_scan(app->iface, ¶ms, + app_action_generic_interface_result, action); + g_bytes_unref(ssid); + return call; +} + +static +GCancellable* +app_action_dump_properties( + AppAction* action) +{ + App* app = action->app; + if (app->iface) { + if (app->iface->present) { + GSUPPLICANT_INTERFACE_PROPERTY p; + for (p = GSUPPLICANT_INTERFACE_PROPERTY_ANY+1; + p < GSUPPLICANT_INTERFACE_PROPERTY_COUNT; + p++) { + dump_interface_property(app->iface, p); + } + } else { + printf("%s is not present\n", app->iface->path); + } + } + if (app->bss) { + if (app->bss->present) { + GSUPPLICANT_BSS_PROPERTY p; + for (p = GSUPPLICANT_BSS_PROPERTY_ANY+1; + p < GSUPPLICANT_BSS_PROPERTY_COUNT; + p++) { + dump_bss_property(app->bss, p); + } + } else { + printf("%s is not present\n", app->bss->path); + } + } + if (app->network) { + if (app->network->present) { + GSUPPLICANT_NETWORK_PROPERTY p; + for (p = GSUPPLICANT_NETWORK_PROPERTY_ANY+1; + p < GSUPPLICANT_NETWORK_PROPERTY_COUNT; + p++) { + dump_network_property(app->network, p); + } + } else { + printf("%s is not present\n", app->network->path); + } + } + return NULL; +} + +static +void +interface_property_changed( + GSupplicantInterface* iface, + GSUPPLICANT_INTERFACE_PROPERTY property, + void* arg) +{ + dump_interface_property(iface, property); +} + +static +void +interface_invalid_exit( + GSupplicantInterface* iface, + void* arg) +{ + if (!iface->valid) { + App* app = arg; + gsupplicant_interface_remove_handler(iface, app->iface_valid_signal_id); + app->iface_valid_signal_id = 0; + GDEBUG("Interface %s is invalid, exiting...", iface->path); + g_main_loop_quit(app->loop); + } +} + +static +void +bss_property_changed( + GSupplicantBSS* bss, + GSUPPLICANT_BSS_PROPERTY property, + void* arg) +{ + dump_bss_property(bss, property); +} + +static +void +bss_invalid_exit( + GSupplicantBSS* bss, + void* arg) +{ + if (!bss->valid) { + App* app = arg; + gsupplicant_bss_remove_handlers(bss, &app->bss_valid_signal_id, 1); + GDEBUG("BSS %s is invalid, exiting...", bss->path); + g_main_loop_quit(app->loop); + } +} + +static +void +network_property_changed( + GSupplicantNetwork* bss, + GSUPPLICANT_NETWORK_PROPERTY property, + void* arg) +{ + dump_network_property(bss, property); +} + +static +void +network_invalid_exit( + GSupplicantNetwork* network, + void* arg) +{ + if (!network->valid) { + App* app = arg; + gsupplicant_network_remove_handlers(network, + &app->network_valid_signal_id, 1); + GDEBUG("Network %s is invalid, exiting...", network->path); + g_main_loop_quit(app->loop); + } +} + +static +void +app_follow( + App* app) +{ + if (app->iface) { + app->iface_valid_signal_id = + gsupplicant_interface_add_handler(app->iface, + GSUPPLICANT_INTERFACE_PROPERTY_VALID, + interface_invalid_exit, app); + app->iface_prop_signal_id = + gsupplicant_interface_add_property_changed_handler(app->iface, + GSUPPLICANT_INTERFACE_PROPERTY_ANY, + interface_property_changed, app); + } + if (app->bss) { + app->bss_valid_signal_id = + gsupplicant_bss_add_handler(app->bss, + GSUPPLICANT_BSS_PROPERTY_VALID, + bss_invalid_exit, app); + app->bss_prop_signal_id = + gsupplicant_bss_add_property_changed_handler(app->bss, + GSUPPLICANT_BSS_PROPERTY_ANY, + bss_property_changed, app); + } + if (app->network) { + app->network_valid_signal_id = + gsupplicant_network_add_handler(app->network, + GSUPPLICANT_NETWORK_PROPERTY_VALID, + network_invalid_exit, app); + app->network_prop_signal_id = + gsupplicant_network_add_property_changed_handler(app->network, + GSUPPLICANT_NETWORK_PROPERTY_ANY, + network_property_changed, app); + } +} + +static +void +interface_valid_handler( + GSupplicantInterface* iface, + void* arg) +{ + if (iface->valid) { + App* app = arg; + gsupplicant_interface_remove_handlers(iface, + &app->iface_valid_signal_id, 1); + app->iface_valid_signal_id = 0; + app_next_action(app); + } +} + +static +void +bss_valid_handler( + GSupplicantBSS* bss, + void* arg) +{ + if (bss->valid) { + App* app = arg; + gsupplicant_bss_remove_handlers(bss, &app->bss_valid_signal_id, 1); + app_next_action(app); + } +} + +static +void +network_valid_handler( + GSupplicantNetwork* network, + void* arg) +{ + if (network->valid) { + App* app = arg; + gsupplicant_network_remove_handlers(network, + &app->network_valid_signal_id, 1); + app_next_action(app); + } +} + +static +void +supplicant_valid( + App* app) +{ + GSupplicant* sup = app->supplicant; + GDEBUG("Supplicant is running"); + if (sup->failed) { + GERR("Not authorized?"); + app->ret = RET_ERR; + } else { + app->ret = RET_OK; + if (app->pick_interface) { + if (sup->interfaces && sup->interfaces[0]) { + g_free(app->iface_path); + app->iface_path = g_strdup(sup->interfaces[0]); + GDEBUG("Picked %s", app->iface_path); + } + } + if (app->iface_path) { + if (gutil_strv_contains(sup->interfaces, app->iface_path)) { + app->iface = gsupplicant_interface_new(app->iface_path); + if (app->iface->valid) { + app_next_action(app); + } else { + GDEBUG("Waiting for %s", app->iface_path); + app->iface_valid_signal_id = + gsupplicant_interface_add_handler(app->iface, + GSUPPLICANT_INTERFACE_PROPERTY_VALID, + interface_valid_handler, app); + } + } else { + GERR("Interface %s not found", app->iface_path); + app->ret = RET_NOTFOUND; + } + } + if (app->bss_path) { + app->bss = gsupplicant_bss_new(app->bss_path); + if (app->bss->valid) { + app_next_action(app); + } else { + GDEBUG("Waiting for %s", app->bss_path); + app->bss_valid_signal_id = + gsupplicant_bss_add_handler(app->bss, + GSUPPLICANT_BSS_PROPERTY_VALID, + bss_valid_handler, app); + } + } + if (app->network_path) { + app->network = gsupplicant_network_new(app->network_path); + if (app->network->valid) { + app_next_action(app); + } else { + GDEBUG("Waiting for %s", app->network_path); + app->network_valid_signal_id = + gsupplicant_network_add_handler(app->network, + GSUPPLICANT_NETWORK_PROPERTY_VALID, + network_valid_handler, app); + } + } + if (app->dump_properties) { + if (!app->iface_path && !app->bss_path && !app->network_path) { + printf("Interfaces:"); + if (sup->interfaces) { + const char* delimiter = " "; + char* const* ptr = sup->interfaces; + while (*ptr) { + printf("%s%s", delimiter, *ptr++); + delimiter = ","; + } + } + printf("\nCapabilities:"); + dump_supplicant_caps(sup->caps, " ", ","); + printf("\nEAP Methods:"); + dump_supplicant_eap_methods(sup->eap_methods, " ", ","); + printf("\n"); + } else { + if (app->list_interfaces) { + GDEBUG("Interfaces:"); + if (sup->interfaces) { + char* const* ptr = sup->interfaces; + while (*ptr) printf("%s\n", *ptr++); + } + } + if (app->list_caps) { + GDEBUG("Capabilities:"); + dump_supplicant_caps(sup->caps, "", "\n"); + } + if (app->list_eap_methods) { + GDEBUG("EAP Methods:"); + dump_supplicant_eap_methods(sup->eap_methods, "", "\n"); + } + } + } + } + if (!app->iface_valid_signal_id && + !app->bss_valid_signal_id && + !app->network_valid_signal_id) { + if (app->actions) { + app_next_action(app); + } else { + g_main_loop_quit(app->loop); + } + } +} + +static +void +supplicant_valid_handler( + GSupplicant* supplicant, + void* arg) +{ + if (supplicant->valid) { + App* app = arg; + gsupplicant_remove_handler(supplicant, app->supplicant_valid_signal_id); + app->supplicant_valid_signal_id = 0; + supplicant_valid(app); + } +} + +static +int +app_run( + App* app) +{ + app->supplicant = gsupplicant_new(); + app->loop = g_main_loop_new(NULL, FALSE); + if (app->timeout > 0) GDEBUG("Timeout %d sec", app->timeout); + if (app->supplicant->valid) { + supplicant_valid(app); + } else { + app->supplicant_valid_signal_id = + gsupplicant_add_handler(app->supplicant, + GSUPPLICANT_PROPERTY_VALID, supplicant_valid_handler, app); + } + g_main_loop_run(app->loop); + gsupplicant_remove_handler(app->supplicant, + app->supplicant_valid_signal_id); + if (app->iface) { + gsupplicant_interface_remove_handler(app->iface, + app->iface_valid_signal_id); + gsupplicant_interface_remove_handler(app->iface, + app->iface_prop_signal_id); + gsupplicant_interface_unref(app->iface); + } + if (app->bss) { + gsupplicant_bss_remove_handler(app->bss, + app->bss_valid_signal_id); + gsupplicant_bss_remove_handler(app->bss, + app->bss_prop_signal_id); + gsupplicant_bss_unref(app->bss); + } + if (app->network) { + gsupplicant_network_remove_handler(app->network, + app->network_valid_signal_id); + gsupplicant_network_remove_handler(app->network, + app->network_prop_signal_id); + gsupplicant_network_unref(app->network); + } + g_main_loop_unref(app->loop); + gsupplicant_unref(app->supplicant); + return app->ret; +} + +static +gboolean +app_log_verbose( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + gutil_log_default.level = GLOG_LEVEL_VERBOSE; + return TRUE; +} + +static +gboolean +app_log_quiet( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + gutil_log_default.level = GLOG_LEVEL_ERR; + return TRUE; +} + +static +gboolean +app_option_signal_poll( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_new(app, app_action_signal_poll)); + return TRUE; +} + +static +gboolean +app_option_country( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_str_new(app, app_action_country, value)); + return TRUE; +} + +static +gboolean +app_option_ap_scan( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + if (value) { + const int ap_scan = atoi(value); + if (ap_scan >= 0) { + app_add_action(app, app_action_int_new(app, app_action_ap_scan, + ap_scan)); + return TRUE; + } else { + g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Invalid ap_scan value \'%s\'", value); + } + } else { + g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Missing ap_scan value"); + } + return FALSE; +} + +static +gboolean +app_option_passive_scan( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_new(app, app_action_passive_scan)); + return TRUE; +} + +static +gboolean +app_option_active_scan( + const gchar* name, + const gchar* ssid, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_str_new(app, app_action_active_scan, ssid)); + return TRUE; +} + +static +gboolean +app_option_create_interface( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_str_new(app, + app_action_create_interface, value)); + return TRUE; +} + +static +gboolean +app_option_get_interface( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_str_new(app, + app_action_get_interface, value)); + return TRUE; +} + +static +gboolean +app_option_remove_interface( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app_add_action(app, app_action_str_new(app, + app_action_remove_interface, value)); + return TRUE; +} + +static +gboolean +app_option_dump_properties( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + App* app = data; + app->dump_properties = TRUE; + app_add_action(app, app_action_new(app, app_action_dump_properties)); + return TRUE; +} + +static +gboolean +app_init( + App* app, + int argc, + char* argv[]) +{ + gboolean ok = FALSE; + GOptionEntry entries[] = { + { "verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + app_log_verbose, "Enable verbose output", NULL }, + { "quiet", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + app_log_quiet, "Be quiet", NULL }, + { "timeout", 't', 0, G_OPTION_ARG_INT, + &app->timeout, "Timeout in seconds", "SEC" }, + { "list", 'l', 0, G_OPTION_ARG_NONE, &app->list_interfaces, + "List interfaces", NULL }, + { "interface", 'i', 0, G_OPTION_ARG_STRING, &app->iface_path, + "Select interface", "PATH" }, + { "bss", 'b', 0, G_OPTION_ARG_STRING, &app->bss_path, + "Select BSS", "PATH" }, + { "network", 'n', 0, G_OPTION_ARG_STRING, &app->network_path, + "Select network", "PATH" }, + { "capabilities", 'c', 0, G_OPTION_ARG_NONE, &app->list_caps, + "List capabilities", NULL }, + { "eap-methods", 'm', 0, G_OPTION_ARG_NONE, &app->list_eap_methods, + "List EAP methods", NULL }, + { "follow", 'f', 0, G_OPTION_ARG_NONE, &app->follow_properties, + "Follow property changes", NULL }, + { NULL } + }; + GOptionEntry interface_entries[] = { + { "properties", 'p', G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_IN_MAIN, + G_OPTION_ARG_CALLBACK, app_option_dump_properties, + "Dump properties of the selected object", NULL }, + { "pick-interface", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, + &app->pick_interface, "Pick the first available interface", NULL }, + { "create-interface", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, + app_option_create_interface, "Create interface for IFNAME", + "IFNAME" }, + { "get-interface", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, + app_option_get_interface, "Get interface path for the IFNAME", + "IFNAME" }, + { "remove-interface", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, + app_option_remove_interface, "Remove interface", "PATH" }, + { "signal-poll", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + app_option_signal_poll, "Show signal poll values", NULL }, + { "ap-scan", 0, 0, G_OPTION_ARG_CALLBACK, + app_option_ap_scan, "Set ap_scan parameter", NULL }, + { "passive-scan", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + app_option_passive_scan, "Perform passive scan", NULL }, + { "active-scan", 0, 0, G_OPTION_ARG_CALLBACK, + app_option_active_scan, "Perform active scan for SSID", "SSID" }, + { "country", 0, 0, G_OPTION_ARG_CALLBACK, + app_option_country, "Set the country", "COUNTRY" }, + { NULL } + }; + GError* error = NULL; + GOptionContext* options = g_option_context_new(NULL); + GOptionGroup* interface_group = g_option_group_new("interface", + "Interface Options:", "Show interface options", app, NULL); + g_option_context_add_main_entries(options, entries, NULL); + g_option_group_add_entries(interface_group, interface_entries); + g_option_context_add_group(options, interface_group); + if (g_option_context_parse(options, &argc, &argv, &error)) { + if (argc == 1 && !(app->bss_path && app->iface_path)) { + ok = TRUE; + } else { + char* help = g_option_context_get_help(options, TRUE, NULL); + fprintf(stderr, "%s", help); + g_free(help); + } + } else { + GERR("%s", error->message); + g_error_free(error); + } + g_option_context_free(options); + return ok; +} + +int main(int argc, char* argv[]) +{ + int ret = RET_ERR; + App app; + memset(&app, 0, sizeof(app)); + app.timeout = -1; + gutil_log_timestamp = FALSE; + gutil_log_set_type(GLOG_TYPE_STDERR, "wpa-tool"); + gutil_log_default.level = GLOG_LEVEL_DEFAULT; + if (app_init(&app, argc, argv)) { + ret = app_run(&app); + } + g_free(app.iface_path); + g_free(app.bss_path); + g_free(app.network_path); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */