/* * * Wireless daemon for Linux * * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "linux/nl80211.h" #include "src/iwd.h" #include "src/module.h" #include "src/netdev.h" #include "src/wiphy.h" #include "src/crypto.h" #include "src/ie.h" #include "src/util.h" #include "src/eapol.h" #include "src/handshake.h" #include "src/mpdu.h" #include "src/dbus.h" #include "src/nl80211util.h" struct adhoc_state { struct netdev *netdev; struct l_genl_family *nl80211; char *ssid; uint8_t pmk[32]; struct l_queue *sta_states; uint32_t sta_watch_id; uint32_t netdev_watch_id; struct l_dbus_message *pending; uint32_t ciphers; uint32_t group_cipher; uint8_t gtk[CRYPTO_MAX_GTK_LEN]; uint8_t gtk_index; bool started : 1; bool open : 1; bool gtk_set : 1; }; struct sta_state { uint8_t addr[6]; struct adhoc_state *adhoc; struct eapol_sm *sm; struct handshake_state *hs_sta; struct eapol_sm *sm_a; struct handshake_state *hs_auth; uint32_t gtk_query_cmd_id; bool hs_sta_done : 1; bool hs_auth_done : 1; bool authenticated : 1; }; static uint32_t netdev_watch; static void adhoc_sta_free(void *data) { struct sta_state *sta = data; if (sta->adhoc->open) goto end; if (sta->gtk_query_cmd_id) l_genl_family_cancel(sta->adhoc->nl80211, sta->gtk_query_cmd_id); if (sta->sm) eapol_sm_free(sta->sm); if (sta->hs_sta) handshake_state_free(sta->hs_sta); if (sta->sm_a) eapol_sm_free(sta->sm_a); if (sta->hs_auth) handshake_state_free(sta->hs_auth); end: l_free(sta); } static void adhoc_remove_sta(struct sta_state *sta) { if (!l_queue_remove(sta->adhoc->sta_states, sta)) { l_error("station %p was not found", sta); return; } if (sta->gtk_query_cmd_id) { l_genl_family_cancel(sta->adhoc->nl80211, sta->gtk_query_cmd_id); sta->gtk_query_cmd_id = 0; } /* signal station has been removed */ if (sta->authenticated) { l_dbus_property_changed(dbus_get_bus(), netdev_get_path(sta->adhoc->netdev), IWD_ADHOC_INTERFACE, "ConnectedPeers"); } adhoc_sta_free(sta); } static void adhoc_reset(struct adhoc_state *adhoc) { if (adhoc->pending) dbus_pending_reply(&adhoc->pending, dbus_error_aborted(adhoc->pending)); l_free(adhoc->ssid); adhoc->ssid = NULL; netdev_station_watch_remove(adhoc->netdev, adhoc->sta_watch_id); l_queue_destroy(adhoc->sta_states, adhoc_sta_free); adhoc->sta_states = NULL; adhoc->started = false; l_dbus_property_changed(dbus_get_bus(), netdev_get_path(adhoc->netdev), IWD_ADHOC_INTERFACE, "Started"); } static void adhoc_set_rsn_info(struct adhoc_state *adhoc, struct ie_rsn_info *rsn) { memset(rsn, 0, sizeof(*rsn)); rsn->akm_suites = IE_RSN_AKM_SUITE_PSK; rsn->pairwise_ciphers = adhoc->ciphers; rsn->group_cipher = adhoc->group_cipher; } static bool ap_sta_match_addr(const void *a, const void *b) { const struct sta_state *sta = a; return !memcmp(sta->addr, b, 6); } static void adhoc_operstate_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { if (!error) return; l_debug("netdev: %u, error: %s", L_PTR_TO_UINT(user_data), strerror(-error)); } static void adhoc_handshake_event(struct handshake_state *hs, enum handshake_event event, void *user_data, ...) { struct sta_state *sta = user_data; struct adhoc_state *adhoc = sta->adhoc; va_list args; va_start(args, user_data); switch (event) { case HANDSHAKE_EVENT_FAILED: l_error("handshake failed with STA "MAC" (%d)", MAC_STR(sta->addr), va_arg(args, int)); /* * eapol frees the state machines upon handshake failure. Since * this is only a failure on one of the handshakes we need to * set the failing SM to NULL so it will not get double freed * by adhoc_remove_sta. */ if (sta->hs_auth == hs) sta->sm_a = NULL; else sta->sm = NULL; /* fall through */ case HANDSHAKE_EVENT_SETTING_KEYS_FAILED: adhoc_remove_sta(sta); return; case HANDSHAKE_EVENT_COMPLETE: if (sta->hs_auth == hs) sta->hs_auth_done = true; if (sta->hs_sta == hs) sta->hs_sta_done = true; if ((sta->hs_auth_done && sta->hs_sta_done) && !sta->authenticated) { sta->authenticated = true; l_dbus_property_changed(dbus_get_bus(), netdev_get_path(adhoc->netdev), IWD_ADHOC_INTERFACE, "ConnectedPeers"); } break; default: break; } } static struct eapol_sm *adhoc_new_sm(struct sta_state *sta, bool authenticator, const uint8_t *gtk_rsc) { struct adhoc_state *adhoc = sta->adhoc; struct netdev *netdev = adhoc->netdev; const uint8_t *own_addr = netdev_get_address(netdev); struct ie_rsn_info rsn; uint8_t bss_rsne[24]; struct handshake_state *hs; struct eapol_sm *sm; /* fill in only what handshake setup requires */ adhoc_set_rsn_info(adhoc, &rsn); ie_build_rsne(&rsn, bss_rsne); hs = netdev_handshake_state_new(netdev); if (!hs) { l_error("could not create handshake object"); return NULL; } handshake_state_set_event_func(hs, adhoc_handshake_event, sta); handshake_state_set_ssid(hs, (void *)adhoc->ssid, strlen(adhoc->ssid)); /* we don't have the connecting peer rsn info, so just set ap == own */ handshake_state_set_authenticator_ie(hs, bss_rsne); handshake_state_set_supplicant_ie(hs, bss_rsne); handshake_state_set_pmk(hs, adhoc->pmk, 32); if (authenticator) { handshake_state_set_authenticator_address(hs, own_addr); handshake_state_set_supplicant_address(hs, sta->addr); handshake_state_set_authenticator(hs, true); } else { handshake_state_set_authenticator_address(hs, sta->addr); handshake_state_set_supplicant_address(hs, own_addr); } if (gtk_rsc) handshake_state_set_gtk(hs, adhoc->gtk, adhoc->gtk_index, gtk_rsc); sm = eapol_sm_new(hs); if (!sm) { l_error("could not create sm object"); return NULL; } eapol_sm_set_listen_interval(sm, 100); if (authenticator) sta->hs_auth = hs; else sta->hs_sta = hs; return sm; } static void adhoc_free(struct adhoc_state *adhoc) { adhoc_reset(adhoc); l_genl_family_free(adhoc->nl80211); l_free(adhoc); } static void adhoc_start_rsna(struct sta_state *sta, const uint8_t *gtk_rsc) { sta->sm_a = adhoc_new_sm(sta, true, gtk_rsc); if (!sta->sm_a) { l_error("could not create authenticator state machine"); goto failed; } sta->sm = adhoc_new_sm(sta, false, NULL); if (!sta->sm) { l_error("could not create station state machine"); goto failed; } eapol_register(sta->sm); eapol_register(sta->sm_a); eapol_start(sta->sm); eapol_start(sta->sm_a); return; failed: adhoc_remove_sta(sta); } static void adhoc_gtk_op_cb(struct l_genl_msg *msg, void *user_data) { if (l_genl_msg_get_error(msg) < 0) { uint8_t cmd = l_genl_msg_get_command(msg); const char *cmd_name = cmd == NL80211_CMD_NEW_KEY ? "NEW_KEY" : "SET_KEY"; l_error("%s failed for the GTK: %i", cmd_name, l_genl_msg_get_error(msg)); } } static void adhoc_gtk_query_cb(struct l_genl_msg *msg, void *user_data) { struct sta_state *sta = user_data; const void *gtk_rsc; sta->gtk_query_cmd_id = 0; gtk_rsc = nl80211_parse_get_key_seq(msg); if (!gtk_rsc) goto error; adhoc_start_rsna(sta, gtk_rsc); return; error: adhoc_remove_sta(sta); } static void adhoc_new_station(struct adhoc_state *adhoc, const uint8_t *mac) { struct sta_state *sta; struct l_genl_msg *msg; sta = l_queue_find(adhoc->sta_states, ap_sta_match_addr, mac); if (sta) { l_warn("new station event with already connected STA"); return; } /* * Follows same logic as AP. If this is the first station we create and * set a group key. Any subsequent connections will use GET_KEY for this * tx GTK. */ if (adhoc->group_cipher != IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC && !adhoc->gtk_set && !adhoc->open) { enum crypto_cipher group_cipher = ie_rsn_cipher_suite_to_cipher(adhoc->group_cipher); int gtk_len = crypto_cipher_key_len(group_cipher); /* * Generate our GTK. Not following the example derivation * method in 802.11-2016 section 12.7.1.4 because a simple * l_getrandom is just as good. */ l_getrandom(adhoc->gtk, gtk_len); adhoc->gtk_index = 1; msg = nl80211_build_new_key_group( netdev_get_ifindex(adhoc->netdev), group_cipher, adhoc->gtk_index, adhoc->gtk, gtk_len, NULL, 0, NULL); if (!l_genl_family_send(adhoc->nl80211, msg, adhoc_gtk_op_cb, NULL, NULL)) { l_genl_msg_unref(msg); l_error("Issuing NEW_KEY failed"); return; } msg = nl80211_build_set_key(netdev_get_ifindex(adhoc->netdev), adhoc->gtk_index); if (!l_genl_family_send(adhoc->nl80211, msg, adhoc_gtk_op_cb, NULL, NULL)) { l_genl_msg_unref(msg); l_error("Issuing SET_KEY failed"); return; } /* * Set the flag now because any new associating STA will * just use NL80211_CMD_GET_KEY from now. */ adhoc->gtk_set = true; } sta = l_new(struct sta_state, 1); memset(sta, 0, sizeof(struct sta_state)); memcpy(sta->addr, mac, 6); sta->adhoc = adhoc; l_queue_push_tail(adhoc->sta_states, sta); l_info("new Station: "MAC" adhoc=%p", MAC_STR(mac), adhoc); /* with open networks nothing else is required */ if (sta->adhoc->open) { int ifindex = netdev_get_ifindex(adhoc->netdev); sta->authenticated = true; l_rtnl_set_linkmode_and_operstate(iwd_get_rtnl(), ifindex, IF_LINK_MODE_DORMANT, IF_OPER_UP, adhoc_operstate_cb, L_UINT_TO_PTR(ifindex), NULL); l_dbus_property_changed(dbus_get_bus(), netdev_get_path(adhoc->netdev), IWD_ADHOC_INTERFACE, "ConnectedPeers"); return; } if (adhoc->group_cipher == IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC) adhoc_start_rsna(sta, NULL); else { msg = nl80211_build_get_key(netdev_get_ifindex(adhoc->netdev), adhoc->gtk_index); sta->gtk_query_cmd_id = l_genl_family_send(adhoc->nl80211, msg, adhoc_gtk_query_cb, sta, NULL); if (!sta->gtk_query_cmd_id) { l_genl_msg_unref(msg); l_error("Issuing GET_KEY failed"); adhoc_remove_sta(sta); return; } } } static void adhoc_del_station(struct adhoc_state *adhoc, const uint8_t *mac) { struct sta_state *sta; sta = l_queue_find(adhoc->sta_states, ap_sta_match_addr, mac); if (!sta) { l_warn("could not find station "MAC" in list", MAC_STR(mac)); return; } l_debug("lost station "MAC, MAC_STR(mac)); adhoc_remove_sta(sta); } static void adhoc_station_changed_cb(struct netdev *netdev, const uint8_t *mac, bool added, void *user_data) { struct adhoc_state *adhoc = user_data; if (added) adhoc_new_station(adhoc, mac); else adhoc_del_station(adhoc, mac); } static void adhoc_join_cb(struct netdev *netdev, int result, void *user_data) { struct adhoc_state *adhoc = user_data; struct l_dbus_message *reply; if (result < 0) { l_error("Failed to join adhoc network, %i", result); dbus_pending_reply(&adhoc->pending, dbus_error_failed(adhoc->pending)); return; } adhoc->sta_watch_id = netdev_station_watch_add(netdev, adhoc_station_changed_cb, adhoc); reply = l_dbus_message_new_method_return(adhoc->pending); dbus_pending_reply(&adhoc->pending, reply); adhoc->started = true; l_dbus_property_changed(dbus_get_bus(), netdev_get_path(adhoc->netdev), IWD_ADHOC_INTERFACE, "Started"); } static struct l_dbus_message *adhoc_dbus_start(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { struct adhoc_state *adhoc = user_data; struct netdev *netdev = adhoc->netdev; struct wiphy *wiphy = netdev_get_wiphy(netdev); const char *ssid, *wpa2_psk; struct ie_rsn_info rsn; struct iovec rsn_ie; uint8_t ie_elems[32]; if (adhoc->pending) return dbus_error_busy(message); if (!l_dbus_message_get_arguments(message, "ss", &ssid, &wpa2_psk)) return dbus_error_invalid_args(message); adhoc->ssid = l_strdup(ssid); adhoc->pending = l_dbus_message_ref(message); adhoc->sta_states = l_queue_new(); adhoc->ciphers = wiphy_select_cipher(wiphy, 0xffff); adhoc->group_cipher = wiphy_select_cipher(wiphy, 0xffff); adhoc_set_rsn_info(adhoc, &rsn); ie_build_rsne(&rsn, ie_elems); rsn_ie.iov_base = ie_elems; rsn_ie.iov_len = ie_elems[1] + 2; if (crypto_psk_from_passphrase(wpa2_psk, (uint8_t *) ssid, strlen(ssid), adhoc->pmk)) return dbus_error_invalid_args(message); if (netdev_join_adhoc(netdev, ssid, &rsn_ie, 1, true, adhoc_join_cb, adhoc)) return dbus_error_invalid_args(message); return NULL; } static struct l_dbus_message *adhoc_dbus_start_open(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { struct adhoc_state *adhoc = user_data; struct netdev *netdev = adhoc->netdev; const char *ssid; struct iovec rsn_ie; uint8_t ie_elems[10]; if (adhoc->pending) return dbus_error_busy(message); if (!l_dbus_message_get_arguments(message, "s", &ssid)) return dbus_error_invalid_args(message); adhoc->ssid = l_strdup(ssid); adhoc->pending = l_dbus_message_ref(message); adhoc->sta_states = l_queue_new(); adhoc->open = true; /* Mac/iPhone seem to require the extended capabilities field */ memset(ie_elems, 0, sizeof(ie_elems)); ie_elems[0] = IE_TYPE_EXTENDED_CAPABILITIES; ie_elems[1] = 8; rsn_ie.iov_base = ie_elems; rsn_ie.iov_len = ie_elems[1] + 2; if (netdev_join_adhoc(netdev, ssid, &rsn_ie, 1, false, adhoc_join_cb, adhoc)) return dbus_error_invalid_args(message); return NULL; } static void adhoc_leave_cb(struct netdev *netdev, int result, void *user_data) { struct adhoc_state *adhoc = user_data; if (result < 0) { l_error("Failed to leave adhoc network, %i", result); dbus_pending_reply(&adhoc->pending, dbus_error_failed(adhoc->pending)); return; } dbus_pending_reply(&adhoc->pending, l_dbus_message_new_method_return(adhoc->pending)); adhoc_reset(adhoc); } static struct l_dbus_message *adhoc_dbus_stop(struct l_dbus *dbus, struct l_dbus_message *message, void *user_data) { struct adhoc_state *adhoc = user_data; if (adhoc->pending) return dbus_error_busy(message); /* already stopped, no-op */ if (!adhoc->started) return l_dbus_message_new_method_return(message); if (netdev_leave_adhoc(adhoc->netdev, adhoc_leave_cb, adhoc)) return dbus_error_failed(message); adhoc->pending = l_dbus_message_ref(message); return NULL; } static void sta_append(void *data, void *user_data) { struct sta_state *sta = data; struct l_dbus_message_builder *builder = user_data; const char* macstr; if (!sta->authenticated) return; macstr = util_address_to_string(sta->addr); l_dbus_message_builder_append_basic(builder, 's', macstr); } static bool adhoc_property_get_peers(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct adhoc_state *adhoc = user_data; l_dbus_message_builder_enter_array(builder, "s"); l_queue_foreach(adhoc->sta_states, sta_append, builder); l_dbus_message_builder_leave_array(builder); return true; } static bool adhoc_property_get_started(struct l_dbus *dbus, struct l_dbus_message *message, struct l_dbus_message_builder *builder, void *user_data) { struct adhoc_state *adhoc = user_data; bool started = adhoc->started; l_dbus_message_builder_append_basic(builder, 'b', &started); return true; } static void adhoc_setup_interface(struct l_dbus_interface *interface) { l_dbus_interface_method(interface, "Start", 0, adhoc_dbus_start, "", "ss", "ssid", "wpa2_psk"); l_dbus_interface_method(interface, "Stop", 0, adhoc_dbus_stop, "", ""); l_dbus_interface_method(interface, "StartOpen", 0, adhoc_dbus_start_open, "", "s", "ssid"); l_dbus_interface_property(interface, "ConnectedPeers", 0, "as", adhoc_property_get_peers, NULL); l_dbus_interface_property(interface, "Started", 0, "b", adhoc_property_get_started, NULL); } static void adhoc_destroy_interface(void *user_data) { struct adhoc_state *adhoc = user_data; adhoc_free(adhoc); } static void adhoc_add_interface(struct netdev *netdev) { struct adhoc_state *adhoc; /* just allocate/set device, Start method will complete setup */ adhoc = l_new(struct adhoc_state, 1); adhoc->netdev = netdev; adhoc->nl80211 = l_genl_family_new(iwd_get_genl(), NL80211_GENL_NAME); /* setup adhoc dbus interface */ l_dbus_object_add_interface(dbus_get_bus(), netdev_get_path(netdev), IWD_ADHOC_INTERFACE, adhoc); } static void adhoc_remove_interface(struct netdev *netdev) { l_dbus_object_remove_interface(dbus_get_bus(), netdev_get_path(netdev), IWD_ADHOC_INTERFACE); } static void adhoc_netdev_watch(struct netdev *netdev, enum netdev_watch_event event, void *userdata) { switch (event) { case NETDEV_WATCH_EVENT_UP: case NETDEV_WATCH_EVENT_NEW: if (netdev_get_iftype(netdev) == NETDEV_IFTYPE_ADHOC && netdev_get_is_up(netdev)) adhoc_add_interface(netdev); break; case NETDEV_WATCH_EVENT_DOWN: case NETDEV_WATCH_EVENT_DEL: adhoc_remove_interface(netdev); break; default: break; } } static int adhoc_init(void) { netdev_watch = netdev_watch_add(adhoc_netdev_watch, NULL, NULL); l_dbus_register_interface(dbus_get_bus(), IWD_ADHOC_INTERFACE, adhoc_setup_interface, adhoc_destroy_interface, false); return 0; } static void adhoc_exit(void) { netdev_watch_remove(netdev_watch); l_dbus_unregister_interface(dbus_get_bus(), IWD_ADHOC_INTERFACE); } IWD_MODULE(adhoc, adhoc_init, adhoc_exit) IWD_MODULE_DEPENDS(adhoc, netdev);