/* * WPA Supplicant / dbus-based control interface * Copyright (c) 2006, Dan Williams <dcbw@redhat.com> and Red Hat, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include <dbus/dbus.h> #include "common.h" #include "eloop.h" #include "wps/wps.h" #include "../config.h" #include "../wpa_supplicant_i.h" #include "../bss.h" #include "dbus_old.h" #include "dbus_old_handlers.h" #include "dbus_common_i.h" /** * wpas_dbus_decompose_object_path - Decompose an interface object path into parts * @path: The dbus object path * @network: (out) the configured network this object path refers to, if any * @bssid: (out) the scanned bssid this object path refers to, if any * Returns: The object path of the network interface this path refers to * * For a given object path, decomposes the object path into object id, network, * and BSSID parts, if those parts exist. */ char * wpas_dbus_decompose_object_path(const char *path, char **network, char **bssid) { const unsigned int dev_path_prefix_len = strlen(WPAS_DBUS_PATH_INTERFACES "/"); char *obj_path_only; char *next_sep; /* Be a bit paranoid about path */ if (!path || strncmp(path, WPAS_DBUS_PATH_INTERFACES "/", dev_path_prefix_len)) return NULL; /* Ensure there's something at the end of the path */ if ((path + dev_path_prefix_len)[0] == '\0') return NULL; obj_path_only = os_strdup(path); if (obj_path_only == NULL) return NULL; next_sep = strchr(obj_path_only + dev_path_prefix_len, '/'); if (next_sep != NULL) { const char *net_part = strstr(next_sep, WPAS_DBUS_NETWORKS_PART "/"); const char *bssid_part = strstr(next_sep, WPAS_DBUS_BSSIDS_PART "/"); if (network && net_part) { /* Deal with a request for a configured network */ const char *net_name = net_part + strlen(WPAS_DBUS_NETWORKS_PART "/"); *network = NULL; if (strlen(net_name)) *network = os_strdup(net_name); } else if (bssid && bssid_part) { /* Deal with a request for a scanned BSSID */ const char *bssid_name = bssid_part + strlen(WPAS_DBUS_BSSIDS_PART "/"); if (strlen(bssid_name)) *bssid = os_strdup(bssid_name); else *bssid = NULL; } /* Cut off interface object path before "/" */ *next_sep = '\0'; } return obj_path_only; } /** * wpas_dbus_new_invalid_iface_error - Return a new invalid interface error message * @message: Pointer to incoming dbus message this error refers to * Returns: A dbus error message * * Convenience function to create and return an invalid interface error */ DBusMessage * wpas_dbus_new_invalid_iface_error(DBusMessage *message) { return dbus_message_new_error(message, WPAS_ERROR_INVALID_IFACE, "wpa_supplicant knows nothing about " "this interface."); } /** * wpas_dbus_new_invalid_network_error - Return a new invalid network error message * @message: Pointer to incoming dbus message this error refers to * Returns: a dbus error message * * Convenience function to create and return an invalid network error */ DBusMessage * wpas_dbus_new_invalid_network_error(DBusMessage *message) { return dbus_message_new_error(message, WPAS_ERROR_INVALID_NETWORK, "The requested network does not exist."); } /** * wpas_dbus_new_invalid_bssid_error - Return a new invalid bssid error message * @message: Pointer to incoming dbus message this error refers to * Returns: a dbus error message * * Convenience function to create and return an invalid bssid error */ static DBusMessage * wpas_dbus_new_invalid_bssid_error(DBusMessage *message) { return dbus_message_new_error(message, WPAS_ERROR_INVALID_BSSID, "The BSSID requested was invalid."); } /** * wpas_dispatch_network_method - dispatch messages for configured networks * @message: the incoming dbus message * @wpa_s: a network interface's data * @network_id: id of the configured network we're interested in * Returns: a reply dbus message, or a dbus error message * * This function dispatches all incoming dbus messages for configured networks. */ static DBusMessage * wpas_dispatch_network_method(DBusMessage *message, struct wpa_supplicant *wpa_s, int network_id) { DBusMessage *reply = NULL; const char *method = dbus_message_get_member(message); struct wpa_ssid *ssid; ssid = wpa_config_get_network(wpa_s->conf, network_id); if (ssid == NULL) return wpas_dbus_new_invalid_network_error(message); if (!strcmp(method, "set")) reply = wpas_dbus_iface_set_network(message, wpa_s, ssid); else if (!strcmp(method, "enable")) reply = wpas_dbus_iface_enable_network(message, wpa_s, ssid); else if (!strcmp(method, "disable")) reply = wpas_dbus_iface_disable_network(message, wpa_s, ssid); return reply; } /** * wpas_dispatch_bssid_method - dispatch messages for scanned networks * @message: the incoming dbus message * @wpa_s: a network interface's data * @bssid: bssid of the scanned network we're interested in * Returns: a reply dbus message, or a dbus error message * * This function dispatches all incoming dbus messages for scanned networks. */ static DBusMessage * wpas_dispatch_bssid_method(DBusMessage *message, struct wpa_supplicant *wpa_s, const char *bssid_txt) { u8 bssid[ETH_ALEN]; struct wpa_bss *bss; if (hexstr2bin(bssid_txt, bssid, ETH_ALEN) < 0) return wpas_dbus_new_invalid_bssid_error(message); bss = wpa_bss_get_bssid(wpa_s, bssid); if (bss == NULL) return wpas_dbus_new_invalid_bssid_error(message); /* Dispatch the method call against the scanned bssid */ if (os_strcmp(dbus_message_get_member(message), "properties") == 0) return wpas_dbus_bssid_properties(message, wpa_s, bss); return NULL; } /** * wpas_iface_message_handler - Dispatch messages for interfaces or networks * @connection: Connection to the system message bus * @message: An incoming dbus message * @user_data: A pointer to a dbus control interface data structure * Returns: Whether or not the message was handled * * This function dispatches all incoming dbus messages for network interfaces, * or objects owned by them, such as scanned BSSIDs and configured networks. */ static DBusHandlerResult wpas_iface_message_handler(DBusConnection *connection, DBusMessage *message, void *user_data) { struct wpa_supplicant *wpa_s = user_data; const char *method = dbus_message_get_member(message); const char *path = dbus_message_get_path(message); const char *msg_interface = dbus_message_get_interface(message); char *iface_obj_path = NULL; char *network = NULL; char *bssid = NULL; DBusMessage *reply = NULL; /* Caller must specify a message interface */ if (!msg_interface) goto out; iface_obj_path = wpas_dbus_decompose_object_path(path, &network, &bssid); if (iface_obj_path == NULL) { reply = wpas_dbus_new_invalid_iface_error(message); goto out; } /* Make sure the message's object path actually refers to the * wpa_supplicant structure it's supposed to (which is wpa_s) */ if (wpa_supplicant_get_iface_by_dbus_path(wpa_s->global, iface_obj_path) != wpa_s) { reply = wpas_dbus_new_invalid_iface_error(message); goto out; } if (network && !strcmp(msg_interface, WPAS_DBUS_IFACE_NETWORK)) { /* A method for one of this interface's configured networks */ int nid = strtoul(network, NULL, 10); if (errno != EINVAL) reply = wpas_dispatch_network_method(message, wpa_s, nid); else reply = wpas_dbus_new_invalid_network_error(message); } else if (bssid && !strcmp(msg_interface, WPAS_DBUS_IFACE_BSSID)) { /* A method for one of this interface's scanned BSSIDs */ reply = wpas_dispatch_bssid_method(message, wpa_s, bssid); } else if (!strcmp(msg_interface, WPAS_DBUS_IFACE_INTERFACE)) { /* A method for an interface only. */ if (!strcmp(method, "scan")) reply = wpas_dbus_iface_scan(message, wpa_s); else if (!strcmp(method, "scanResults")) reply = wpas_dbus_iface_scan_results(message, wpa_s); else if (!strcmp(method, "addNetwork")) reply = wpas_dbus_iface_add_network(message, wpa_s); else if (!strcmp(method, "removeNetwork")) reply = wpas_dbus_iface_remove_network(message, wpa_s); else if (!strcmp(method, "selectNetwork")) reply = wpas_dbus_iface_select_network(message, wpa_s); else if (!strcmp(method, "capabilities")) reply = wpas_dbus_iface_capabilities(message, wpa_s); else if (!strcmp(method, "disconnect")) reply = wpas_dbus_iface_disconnect(message, wpa_s); else if (!strcmp(method, "setAPScan")) reply = wpas_dbus_iface_set_ap_scan(message, wpa_s); else if (!strcmp(method, "setSmartcardModules")) reply = wpas_dbus_iface_set_smartcard_modules(message, wpa_s); else if (!strcmp(method, "state")) reply = wpas_dbus_iface_get_state(message, wpa_s); else if (!strcmp(method, "scanning")) reply = wpas_dbus_iface_get_scanning(message, wpa_s); #ifndef CONFIG_NO_CONFIG_BLOBS else if (!strcmp(method, "setBlobs")) reply = wpas_dbus_iface_set_blobs(message, wpa_s); else if (!strcmp(method, "removeBlobs")) reply = wpas_dbus_iface_remove_blobs(message, wpa_s); #endif /* CONFIG_NO_CONFIG_BLOBS */ #ifdef CONFIG_WPS else if (!os_strcmp(method, "wpsPbc")) reply = wpas_dbus_iface_wps_pbc(message, wpa_s); else if (!os_strcmp(method, "wpsPin")) reply = wpas_dbus_iface_wps_pin(message, wpa_s); else if (!os_strcmp(method, "wpsReg")) reply = wpas_dbus_iface_wps_reg(message, wpa_s); #endif /* CONFIG_WPS */ else if (!os_strcmp(method, "flush")) reply = wpas_dbus_iface_flush(message, wpa_s); } /* If the message was handled, send back the reply */ if (reply) { if (!dbus_message_get_no_reply(message)) dbus_connection_send(connection, reply, NULL); dbus_message_unref(reply); } out: os_free(iface_obj_path); os_free(network); os_free(bssid); return reply ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /** * wpas_message_handler - dispatch incoming dbus messages * @connection: connection to the system message bus * @message: an incoming dbus message * @user_data: a pointer to a dbus control interface data structure * Returns: whether or not the message was handled * * This function dispatches all incoming dbus messages to the correct * handlers, depending on what the message's target object path is, * and what the method call is. */ static DBusHandlerResult wpas_message_handler(DBusConnection *connection, DBusMessage *message, void *user_data) { struct wpas_dbus_priv *ctrl_iface = user_data; const char *method; const char *path; const char *msg_interface; DBusMessage *reply = NULL; method = dbus_message_get_member(message); path = dbus_message_get_path(message); msg_interface = dbus_message_get_interface(message); if (!method || !path || !ctrl_iface || !msg_interface) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* Validate the method interface */ if (strcmp(msg_interface, WPAS_DBUS_INTERFACE) != 0) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (!strcmp(path, WPAS_DBUS_PATH)) { /* dispatch methods against our global dbus interface here */ if (!strcmp(method, "addInterface")) { reply = wpas_dbus_global_add_interface( message, ctrl_iface->global); } else if (!strcmp(method, "removeInterface")) { reply = wpas_dbus_global_remove_interface( message, ctrl_iface->global); } else if (!strcmp(method, "getInterface")) { reply = wpas_dbus_global_get_interface( message, ctrl_iface->global); } else if (!strcmp(method, "setDebugParams")) { reply = wpas_dbus_global_set_debugparams( message, ctrl_iface->global); } } /* If the message was handled, send back the reply */ if (reply) { if (!dbus_message_get_no_reply(message)) dbus_connection_send(connection, reply, NULL); dbus_message_unref(reply); } return reply ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /** * wpa_supplicant_dbus_notify_scan_results - Send a scan results signal * @wpa_s: %wpa_supplicant network interface data * Returns: 0 on success, -1 on failure * * Notify listeners that this interface has updated scan results. */ void wpa_supplicant_dbus_notify_scan_results(struct wpa_supplicant *wpa_s) { struct wpas_dbus_priv *iface = wpa_s->global->dbus; DBusMessage *_signal; /* Do nothing if the control interface is not turned on */ if (iface == NULL) return; _signal = dbus_message_new_signal(wpa_s->dbus_path, WPAS_DBUS_IFACE_INTERFACE, "ScanResultsAvailable"); if (_signal == NULL) { wpa_printf(MSG_ERROR, "dbus: Not enough memory to send scan " "results signal"); return; } dbus_connection_send(iface->con, _signal, NULL); dbus_message_unref(_signal); } /** * wpa_supplicant_dbus_notify_state_change - Send a state change signal * @wpa_s: %wpa_supplicant network interface data * @new_state: new state wpa_supplicant is entering * @old_state: old state wpa_supplicant is leaving * Returns: 0 on success, -1 on failure * * Notify listeners that wpa_supplicant has changed state */ void wpa_supplicant_dbus_notify_state_change(struct wpa_supplicant *wpa_s, enum wpa_states new_state, enum wpa_states old_state) { struct wpas_dbus_priv *iface; DBusMessage *_signal = NULL; const char *new_state_str, *old_state_str; if (wpa_s->dbus_path == NULL) return; /* Skip signal since D-Bus setup is not yet ready */ /* Do nothing if the control interface is not turned on */ if (wpa_s->global == NULL) return; iface = wpa_s->global->dbus; if (iface == NULL) return; /* Only send signal if state really changed */ if (new_state == old_state) return; _signal = dbus_message_new_signal(wpa_s->dbus_path, WPAS_DBUS_IFACE_INTERFACE, "StateChange"); if (_signal == NULL) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_state_change: " "could not create dbus signal; likely out of " "memory"); return; } new_state_str = wpa_supplicant_state_txt(new_state); old_state_str = wpa_supplicant_state_txt(old_state); if (new_state_str == NULL || old_state_str == NULL) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_state_change: " "Could not convert state strings"); goto out; } if (!dbus_message_append_args(_signal, DBUS_TYPE_STRING, &new_state_str, DBUS_TYPE_STRING, &old_state_str, DBUS_TYPE_INVALID)) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_state_change: " "Not enough memory to construct state change " "signal"); goto out; } dbus_connection_send(iface->con, _signal, NULL); out: dbus_message_unref(_signal); } /** * wpa_supplicant_dbus_notify_scanning - send scanning status * @wpa_s: %wpa_supplicant network interface data * Returns: 0 on success, -1 on failure * * Notify listeners of interface scanning state changes */ void wpa_supplicant_dbus_notify_scanning(struct wpa_supplicant *wpa_s) { struct wpas_dbus_priv *iface = wpa_s->global->dbus; DBusMessage *_signal; dbus_bool_t scanning = wpa_s->scanning ? TRUE : FALSE; /* Do nothing if the control interface is not turned on */ if (iface == NULL) return; _signal = dbus_message_new_signal(wpa_s->dbus_path, WPAS_DBUS_IFACE_INTERFACE, "Scanning"); if (_signal == NULL) { wpa_printf(MSG_ERROR, "dbus: Not enough memory to send scan " "results signal"); return; } if (dbus_message_append_args(_signal, DBUS_TYPE_BOOLEAN, &scanning, DBUS_TYPE_INVALID)) { dbus_connection_send(iface->con, _signal, NULL); } else { wpa_printf(MSG_ERROR, "dbus: Not enough memory to construct " "signal"); } dbus_message_unref(_signal); } #ifdef CONFIG_WPS void wpa_supplicant_dbus_notify_wps_cred(struct wpa_supplicant *wpa_s, const struct wps_credential *cred) { struct wpas_dbus_priv *iface; DBusMessage *_signal = NULL; /* Do nothing if the control interface is not turned on */ if (wpa_s->global == NULL) return; iface = wpa_s->global->dbus; if (iface == NULL) return; _signal = dbus_message_new_signal(wpa_s->dbus_path, WPAS_DBUS_IFACE_INTERFACE, "WpsCred"); if (_signal == NULL) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_wps_cred: " "Could not create dbus signal; likely out of " "memory"); return; } if (!dbus_message_append_args(_signal, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cred->cred_attr, cred->cred_attr_len, DBUS_TYPE_INVALID)) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_wps_cred: " "Not enough memory to construct signal"); goto out; } dbus_connection_send(iface->con, _signal, NULL); out: dbus_message_unref(_signal); } #else /* CONFIG_WPS */ void wpa_supplicant_dbus_notify_wps_cred(struct wpa_supplicant *wpa_s, const struct wps_credential *cred) { } #endif /* CONFIG_WPS */ void wpa_supplicant_dbus_notify_certification(struct wpa_supplicant *wpa_s, int depth, const char *subject, const char *cert_hash, const struct wpabuf *cert) { struct wpas_dbus_priv *iface; DBusMessage *_signal = NULL; const char *hash; const char *cert_hex; int cert_hex_len; /* Do nothing if the control interface is not turned on */ if (wpa_s->global == NULL) return; iface = wpa_s->global->dbus; if (iface == NULL) return; _signal = dbus_message_new_signal(wpa_s->dbus_path, WPAS_DBUS_IFACE_INTERFACE, "Certification"); if (_signal == NULL) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_certification: " "Could not create dbus signal; likely out of " "memory"); return; } hash = cert_hash ? cert_hash : ""; cert_hex = cert ? wpabuf_head(cert) : ""; cert_hex_len = cert ? wpabuf_len(cert) : 0; if (!dbus_message_append_args(_signal, DBUS_TYPE_INT32,&depth, DBUS_TYPE_STRING, &subject, DBUS_TYPE_STRING, &hash, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cert_hex, cert_hex_len, DBUS_TYPE_INVALID)) { wpa_printf(MSG_ERROR, "dbus: wpa_supplicant_dbus_notify_certification: " "Not enough memory to construct signal"); goto out; } dbus_connection_send(iface->con, _signal, NULL); out: dbus_message_unref(_signal); } /** * wpa_supplicant_dbus_ctrl_iface_init - Initialize dbus control interface * @global: Pointer to global data from wpa_supplicant_init() * Returns: 0 on success, -1 on failure * * Initialize the dbus control interface and start receiving commands from * external programs over the bus. */ int wpa_supplicant_dbus_ctrl_iface_init(struct wpas_dbus_priv *iface) { DBusError error; int ret = -1; DBusObjectPathVTable wpas_vtable = { NULL, &wpas_message_handler, NULL, NULL, NULL, NULL }; /* Register the message handler for the global dbus interface */ if (!dbus_connection_register_object_path(iface->con, WPAS_DBUS_PATH, &wpas_vtable, iface)) { wpa_printf(MSG_ERROR, "dbus: Could not set up message " "handler"); return -1; } /* Register our service with the message bus */ dbus_error_init(&error); switch (dbus_bus_request_name(iface->con, WPAS_DBUS_SERVICE, 0, &error)) { case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: ret = 0; break; case DBUS_REQUEST_NAME_REPLY_EXISTS: case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: wpa_printf(MSG_ERROR, "dbus: Could not request service name: " "already registered"); break; default: wpa_printf(MSG_ERROR, "dbus: Could not request service name: " "%s %s", error.name, error.message); break; } dbus_error_free(&error); if (ret != 0) return -1; wpa_printf(MSG_DEBUG, "Providing DBus service '" WPAS_DBUS_SERVICE "'."); return 0; } /** * wpas_dbus_register_new_iface - Register a new interface with dbus * @wpa_s: %wpa_supplicant interface description structure to register * Returns: 0 on success, -1 on error * * Registers a new interface with dbus and assigns it a dbus object path. */ int wpas_dbus_register_iface(struct wpa_supplicant *wpa_s) { struct wpas_dbus_priv *ctrl_iface = wpa_s->global->dbus; DBusConnection * con; u32 next; DBusObjectPathVTable vtable = { NULL, &wpas_iface_message_handler, NULL, NULL, NULL, NULL }; /* Do nothing if the control interface is not turned on */ if (ctrl_iface == NULL) return 0; con = ctrl_iface->con; next = ctrl_iface->next_objid++; /* Create and set the interface's object path */ wpa_s->dbus_path = os_zalloc(WPAS_DBUS_OBJECT_PATH_MAX); if (wpa_s->dbus_path == NULL) return -1; os_snprintf(wpa_s->dbus_path, WPAS_DBUS_OBJECT_PATH_MAX, WPAS_DBUS_PATH_INTERFACES "/%u", next); /* Register the message handler for the interface functions */ if (!dbus_connection_register_fallback(con, wpa_s->dbus_path, &vtable, wpa_s)) { wpa_printf(MSG_ERROR, "dbus: Could not set up message " "handler for interface %s", wpa_s->ifname); return -1; } return 0; } /** * wpas_dbus_unregister_iface - Unregister an interface from dbus * @wpa_s: wpa_supplicant interface structure * Returns: 0 on success, -1 on failure * * Unregisters the interface with dbus */ int wpas_dbus_unregister_iface(struct wpa_supplicant *wpa_s) { struct wpas_dbus_priv *ctrl_iface; DBusConnection *con; /* Do nothing if the control interface is not turned on */ if (wpa_s == NULL || wpa_s->global == NULL) return 0; ctrl_iface = wpa_s->global->dbus; if (ctrl_iface == NULL) return 0; con = ctrl_iface->con; if (!dbus_connection_unregister_object_path(con, wpa_s->dbus_path)) return -1; os_free(wpa_s->dbus_path); wpa_s->dbus_path = NULL; return 0; } /** * wpa_supplicant_get_iface_by_dbus_path - Get a new network interface * @global: Pointer to global data from wpa_supplicant_init() * @path: Pointer to a dbus object path representing an interface * Returns: Pointer to the interface or %NULL if not found */ struct wpa_supplicant * wpa_supplicant_get_iface_by_dbus_path( struct wpa_global *global, const char *path) { struct wpa_supplicant *wpa_s; for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { if (strcmp(wpa_s->dbus_path, path) == 0) return wpa_s; } return NULL; }