/* * FST Manager: hostapd/wpa_supplicant control interface wrapper: CLI based * implementation * * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of The Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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 #include #include #include #include #include #include #include #include #define FST_MGR_COMPONENT "CTRL" #include "fst_manager.h" #include "utils/common.h" #include "common/defs.h" #include "utils/eloop.h" #include "common/wpa_ctrl.h" #include "fst/fst_ctrl_defs.h" #include "fst_ctrl.h" #include "fst_cfgmgr.h" #include "fst_capconfigstore.h" #ifndef DEFAULT_WPAS_CLI_DIR #define DEFAULT_WPAS_CLI_DIR "/var/run/wpa_supplicant" #endif #ifndef DEFAULT_HAPD_CLI_DIR #define DEFAULT_HAPD_CLI_DIR "/var/run/hostapd" #endif static struct wpa_ctrl *ctrl_evt; static struct wpa_ctrl *ctrl_cmd; static unsigned int ctrl_ping_interval; static Boolean ctrl_is_supplicant; static const char FST_CONFIG_SCAN_INTERVAL_KEY_NAME[] = "fst.config.scan_interval"; #define FST_CONFIG_SCAN_INTERVAL "1" /* Sec */ static int scan_interval; static fst_notification_cb_func global_ntfy_cb = NULL; static void *global_ntfy_cb_ctx = NULL; #define defstrcmp(s, d) strncmp((s), (d), sizeof(d) - 1) #ifndef CONFIG_CTRL_IFACE_DIR #define CONFIG_CTRL_IFACE_DIR "/var/run/hostapd" #endif /* CONFIG_CTRL_IFACE_DIR */ static char *get_pval(char *str, const char *pdesc) { char *p = strchr(str, '='); if (p) { *p++ = '\0'; /* strip leading whitespaces */ while (*p == ' ' || *p == '\t') ++p; } else fst_mgr_printf(MSG_ERROR, "bad %s parameter string reported \'%s\'", pdesc, str); return p; } /* parsers */ static int parse_list(char *p, char *res[], size_t size) { int ret = 0; for (;;) { while (isspace(*p)) p++; if (!*p) return ret; if ((size_t)ret == size) return -1; res[ret++] = p; while (*p && !isspace(*p)) p++; if (isspace(*p)) *p++ = '\0'; } return ret; } struct parse_list_proc_ctx { size_t item_size; int (*item_parser) (char *str, void *data); void **output; void *current; }; static int parse_list_proc(char *buf, void *data) { char *list[256]; struct parse_list_proc_ctx *c = data; int ret, i; if (!strncmp(buf, "FAIL", 4)) return -1; ret = parse_list(buf, list, ARRAY_SIZE(list)); if (!ret) { fst_mgr_printf(MSG_DEBUG, "str list is empty"); return 0; } else if (ret < 0) { fst_mgr_printf(MSG_ERROR, "str list overflow"); return -1; } if (!c->current && !(*c->output = c->current = malloc(c->item_size * ret))) { fst_mgr_printf(MSG_ERROR, "cannot allocate mem for %u items", ret); return -1; } for (i = 0; i < ret; i++) { c->item_parser(list[i], c->current); c->current = (u8*)c->current + c->item_size; } return ret; } /* notifications */ static int peer_event_parser(char *str, void *data) { union fst_event_extra *ev = data; if (!defstrcmp(str, FST_CEP_PNAME_CONNECTED)) ev->peer_state.connected = 1; else if (!defstrcmp(str, FST_CEP_PNAME_DISCONNECTED)) ev->peer_state.connected = 0; else { char *p = get_pval(str, "Peer Event"); if (!p) return -1; if (!defstrcmp(str, FST_CEP_PNAME_IFNAME)) os_strlcpy(ev->peer_state.ifname, p, sizeof(ev->peer_state.ifname)); else if (!defstrcmp(str, FST_CEP_PNAME_ADDR)) { if (hwaddr_aton(p, ev->peer_state.addr)) fst_mgr_printf(MSG_ERROR, "bad peer address string \'%s\'", p); } } return 0; } struct session_event_data { enum fst_event_type event_type; uint32_t session_id; union fst_event_extra extra; }; static int session_event_parser(char *str, void *data) { struct session_event_data *ev = data; char *p = get_pval(str, "Session Event"); if (!p) return -1; if (!defstrcmp(p, FST_CTRL_PVAL_NONE)) ; else if (!defstrcmp(str, FST_CES_PNAME_EVT_TYPE)) ev->event_type = fst_session_event_num(p); else if (!defstrcmp(str, FST_CES_PNAME_SESSION_ID)) ev->session_id = strtoul(p, NULL, 0); else if (!defstrcmp(str, FST_CES_PNAME_OLD_STATE)) ev->extra.session_state.old_state = fst_session_state_num(p); else if (!defstrcmp(str, FST_CES_PNAME_NEW_STATE)) ev->extra.session_state.new_state = fst_session_state_num(p); else if (!defstrcmp(str, FST_CES_PNAME_REASON)) ev->extra.session_state.extra.to_initial.reason = fst_reason_num(p); else if (!defstrcmp(str, FST_CES_PNAME_REJECT_CODE)) ev->extra.session_state.extra.to_initial.reject_code = strtoul(p, NULL, 0); else if (!defstrcmp(str, FST_CES_PNAME_INITIATOR)) ev->extra.session_state.extra.to_initial.initiator = defstrcmp(p, FST_CS_PVAL_INITIATOR_LOCAL) ? FST_INITIATOR_REMOTE : FST_INITIATOR_LOCAL; return 0; } static Boolean fst_ctrl_notify(char *buf, size_t len) { struct parse_list_proc_ctx ctx; char *p = buf; memset(&ctx, 0, sizeof(ctx)); fst_mgr_printf(MSG_DEBUG, "recv: %s", buf); if (*p == '<' && (p = strchr(p, '>'))) p++; if (p == NULL) { fst_mgr_printf(MSG_ERROR, "Bad notification level format"); return FALSE; } if (!strncmp(p, WPA_EVENT_TERMINATING, sizeof(WPA_EVENT_TERMINATING) - 1)) { fst_mgr_printf(MSG_ERROR, WPA_EVENT_TERMINATING "received: terminating"); return TRUE; } if (!global_ntfy_cb) return FALSE; if (!strncmp(p, FST_CTRL_EVENT_PEER " ", sizeof(FST_CTRL_EVENT_PEER))) { union fst_event_extra evt_data = { }; ctx.item_parser = peer_event_parser; ctx.current = &evt_data; parse_list_proc(p + sizeof(FST_CTRL_EVENT_PEER), &ctx); global_ntfy_cb(global_ntfy_cb_ctx, FST_INVALID_SESSION_ID, EVENT_PEER_STATE_CHANGED, &evt_data); } else if (!strncmp(p, FST_CTRL_EVENT_SESSION " ", sizeof(FST_CTRL_EVENT_SESSION))) { struct session_event_data evt_data; memset(&evt_data, 0, sizeof(evt_data)); ctx.item_parser = session_event_parser; ctx.current = &evt_data; parse_list_proc(p + sizeof(FST_CTRL_EVENT_SESSION), &ctx); global_ntfy_cb(global_ntfy_cb_ctx, evt_data.session_id, evt_data.event_type, &evt_data.extra); } else if (os_strstr(p, WPA_EVENT_SCAN_STARTED)) { char *ifname; if (defstrcmp(p, "IFNAME")) return FALSE; ifname = get_pval(p, "Ifname"); if (!ifname) return FALSE; p = strchr(ifname, ' '); if (p) *p = '\0'; global_ntfy_cb(global_ntfy_cb_ctx, FST_INVALID_SESSION_ID, EVENT_FST_SCAN_STARTED, ifname); } else if (os_strstr(p, WPA_EVENT_SCAN_RESULTS)) { char *ifname; if (defstrcmp(p, "IFNAME")) return FALSE; ifname = get_pval(p, "Ifname"); if (!ifname) return FALSE; p = strchr(ifname, ' '); if (p) *p = '\0'; global_ntfy_cb(global_ntfy_cb_ctx, FST_INVALID_SESSION_ID, EVENT_FST_SCAN_COMPLETED, ifname); } else if (os_strstr(p, WPA_EVENT_SIGNAL_CHANGE)) { /* above=%d signal=%d noise=%d txrate=%d */ char *ifname; if (defstrcmp(p, "IFNAME")) return FALSE; ifname = get_pval(p, "Ifname"); if (!ifname) return FALSE; p = strchr(ifname, ' '); if (p) *p = '\0'; global_ntfy_cb(global_ntfy_cb_ctx, FST_INVALID_SESSION_ID, EVENT_FST_SIGNAL_CHANGE, ifname); } return FALSE; } int fst_set_notify_cb(fst_notification_cb_func ntfy_cb, void *ntfy_cb_ctx) { global_ntfy_cb = ntfy_cb; global_ntfy_cb_ctx = ntfy_cb_ctx; return 0; } /* commands */ static int do_hostap_command(const char* cmd, size_t cmd_len, char* resp, size_t* resp_len) { int ret; fst_mgr_printf(MSG_DEBUG, "send: %s", cmd); ret = wpa_ctrl_request(ctrl_cmd, cmd, cmd_len, resp, resp_len, NULL); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "command '%s' %s.", cmd, ret == -2 ? "timed out" : "failed"); return ret; } resp[*resp_len] = '\0'; fst_mgr_printf(MSG_DEBUG, "recv (len %zu): %s", *resp_len, resp); return 0; } static int do_command_ex(int (*res_proc) (char *, void *), void *res_data, const char *prefix, const char *fmt, ...) { char buf[1024] = {0}; char cmd[256]; size_t buf_len = sizeof(buf) - 1; int ret = 0; va_list ap; if (prefix) ret = snprintf(cmd, sizeof(cmd), "%s", prefix); va_start(ap, fmt); ret += vsnprintf(cmd + ret, sizeof(cmd) - ret, fmt, ap); va_end(ap); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) { return ret; } if (buf_len == 0) return 0; if (res_proc) return res_proc(buf, res_data); return strncmp(buf, "OK", 2) ? -1 : 0; } #define do_command(proc, data, fmt, ...) \ do_command_ex(proc, data, "FST-MANAGER ", fmt, ##__VA_ARGS__) #define do_simple_command(fmt, ...) \ do_command_ex(NULL, NULL, NULL, fmt, ##__VA_ARGS__) static int session_info_parser(char *str, void *data) { struct fst_session_info *si = data; char *p = get_pval(str, "Session Info"); if (!p) return -1; if (!defstrcmp(p, FST_CTRL_PVAL_NONE)) ; else if (!defstrcmp(str, FST_CSG_PNAME_OLD_PEER_ADDR)) { if (hwaddr_aton(p, si->old_peer_addr)) fst_mgr_printf(MSG_ERROR, "bad own address string \'%s\'", p); } else if (!defstrcmp(str, FST_CSG_PNAME_NEW_PEER_ADDR)) { if (hwaddr_aton(p, si->new_peer_addr)) fst_mgr_printf(MSG_ERROR, "bad peer address string \'%s\'", p); } else if (!defstrcmp(str, FST_CSG_PNAME_NEW_IFNAME)) { os_strlcpy(si->new_ifname, p, sizeof(si->new_ifname)); } else if (!defstrcmp(str, FST_CSG_PNAME_OLD_IFNAME)) { os_strlcpy(si->old_ifname, p, sizeof(si->old_ifname)); } else if (!defstrcmp(str, FST_CSG_PNAME_LLT)) { si->llt = strtoul(p, NULL, 0); } else if (!defstrcmp(str, FST_CSG_PNAME_STATE)) { si->state = fst_session_state_num(p); } else { fst_mgr_printf(MSG_ERROR, "unknown session parameter \'%s\'", str); } return 0; } int fst_session_get_info(u32 session_id, struct fst_session_info *si) { struct parse_list_proc_ctx ctx; int ret; ctx.item_size = 0; ctx.item_parser = session_info_parser; ctx.current = si; si->session_id = session_id; ret = do_command(parse_list_proc, &ctx, FST_CMD_SESSION_GET " %u", session_id); return ret >= 0 ? 0 : -1; } int fst_session_respond(u32 session_id, const char *response_status) { return do_command(NULL, NULL, FST_CMD_SESSION_RESPOND " %u %s", session_id, response_status); } int fst_session_set(u32 session_id, const char *pname, const char *pval) { return do_command(NULL, NULL, FST_CMD_SESSION_SET " %u %s=%s", session_id, pname, pval); } static int session_id_parser(char *str, void *data) { uint32_t *session_id = data; char *end; *session_id = strtoul(str, &end, 0); return *session_id == (uint32_t) - 1 || (*end && !isspace(*end)) ? -1 : 0; } int fst_session_add(const char *group_id, uint32_t * session_id) { return do_command(session_id_parser, session_id, FST_CMD_SESSION_ADD " %s", group_id); } int fst_session_remove(uint32_t session_id) { return do_command(NULL, NULL, FST_CMD_SESSION_REMOVE " %u", session_id); } int fst_session_initiate(u32 session_id) { return do_command(NULL, NULL, FST_CMD_SESSION_INITIATE " %u", session_id); } int fst_session_transfer(u32 session_id) { return do_command(NULL, NULL, FST_CMD_SESSION_TRANSFER " %u", session_id); } int fst_session_teardown(u32 session_id) { return do_command(NULL, NULL, FST_CMD_SESSION_TEARDOWN " %u", session_id); } int fst_get_sessions(const struct fst_group_info *group, uint32_t ** sessions) { struct parse_list_proc_ctx ctx; ctx.item_size = sizeof(*sessions[0]); ctx.item_parser = session_id_parser; ctx.output = (void *)sessions; ctx.current = NULL; return do_command(parse_list_proc, &ctx, FST_CMD_LIST_SESSIONS " %s", group->id); } static int iface_peer_parser(char *str, void *data) { uint8_t *addr = data; if (hwaddr_aton(str, addr)) fst_mgr_printf(MSG_ERROR, "bad peer address string \'%s\'", str); return 0; } int fst_get_iface_peers(const struct fst_group_info *group, struct fst_iface_info *iface, uint8_t **peers) { struct parse_list_proc_ctx ctx; ctx.item_size = ETH_ALEN; ctx.item_parser = iface_peer_parser; ctx.output = (void *)peers; ctx.current = NULL; return do_command(parse_list_proc, &ctx, FST_CMD_IFACE_PEERS " %s %s", group->id, iface->name); } static int parse_peer_mbies(char *buf, void *data) { char **mbies = data; if (data != NULL) *mbies = NULL; if (!strncmp(buf, "FAIL", 4)) return -EINVAL; if (data != NULL) { *mbies = os_strdup(buf); if (!*mbies) return -ENOMEM; } return os_strlen(buf); } int fst_get_peer_mbies(const char *ifname, const uint8_t *peer, char **mbies) { return do_command(parse_peer_mbies, mbies, FST_CMD_GET_PEER_MBIES " %s " MACSTR, ifname, MAC2STR(peer)); } static int iface_parser(char *str, void *data) { struct fst_iface_info *ii = data; char *p, *e, *tokbuf; const char delims[] = "|"; p = strtok_r(str, delims, &tokbuf); if (!p) { fst_mgr_printf(MSG_ERROR, "bad iface name reported \'%s\'", str); return -1; } os_strlcpy(ii->name, p, sizeof(ii->name)); p = strtok_r(NULL, delims, &tokbuf); if (!p) { fst_mgr_printf(MSG_ERROR, "bad iface address reported \'%s\'", str); return -1; } if (hwaddr_aton(p, ii->addr)) { fst_mgr_printf(MSG_ERROR, "bad addr %s: invalid addr string", p); return -1; } p = strtok_r(NULL, delims, &tokbuf); if (!p) { fst_mgr_printf(MSG_ERROR, "bad iface priority reported \'%s\'", str); return -1; } ii->priority = strtoul(p, &e, 0); if (*e != '\0') { fst_mgr_printf(MSG_ERROR, "bad iface string reported \'%s\'", p); return -1; } p = strtok_r(NULL, delims, &tokbuf); if (!p) { fst_mgr_printf(MSG_ERROR, "bad iface llt reported \'%s\'", str); return -1; } ii->llt = strtoul(p, &e, 0); if (*e != '\0') { fst_mgr_printf(MSG_ERROR, "bad iface llt reported \'%s\'", p); return -1; } return 0; } int fst_get_group_ifaces(const struct fst_group_info *group, struct fst_iface_info **ifaces) { struct parse_list_proc_ctx ctx; ctx.item_size = sizeof(*ifaces[0]); ctx.item_parser = iface_parser; ctx.output = (void *)ifaces; ctx.current = NULL; return do_command(parse_list_proc, &ctx, FST_CMD_LIST_IFACES " %s", group->id); } int fst_detach_iface(const struct fst_iface_info *iface) { return do_command_ex(NULL, NULL, "FST-DETACH", " %s", iface->name); } int fst_attach_iface(const struct fst_group_info *group, const struct fst_iface_info *iface) { return do_command_ex(NULL, NULL, "FST-ATTACH", " %s %s llt=%d priority=%d", iface->name, group->id, iface->llt, iface->priority); } static int group_id_parser(char *str, void *data) { os_strlcpy(((struct fst_group_info *)data)->id, str, sizeof(((struct fst_group_info *) data)->id)); return 0; } int fst_get_groups(struct fst_group_info **groups) { struct parse_list_proc_ctx ctx; ctx.item_size = sizeof(*groups[0]); ctx.item_parser = group_id_parser; ctx.output = (void *)groups; ctx.current = NULL; return do_command(parse_list_proc, &ctx, FST_CMD_LIST_GROUPS); } Boolean fst_is_supplicant(void) { return ctrl_is_supplicant; } static int fst_detect_ctrl_type(void) { char buf[4096]; size_t buf_len = sizeof(buf) - 1; const char *cmd = "INTERFACE_LIST"; int ret = 0; ret = do_hostap_command(cmd, os_strlen(cmd), buf, &buf_len); if (ret < 0) { return -1; } if (buf_len < 3) ctrl_is_supplicant = TRUE; else if (strncmp(buf, "BAD", 3) && strncmp(buf, "FAI", 3)) ctrl_is_supplicant = TRUE; else ctrl_is_supplicant = FALSE; return 0; } static int fst_dup_ap_wpa_psk(const char *master, const struct fst_iface_info *iface) { int ret; char buf[64]; ret = do_simple_command("DUP_NETWORK %s %s wpa", master, iface->name); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Dup wpa failed"); return -1; } ret = do_simple_command("DUP_NETWORK %s %s wpa_passphrase", master, iface->name); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Dup wpa_passphrase failed"); /* wpa_passphrase cannot be duplicated, try wpa_psk */ ret = do_simple_command("DUP_NETWORK %s %s wpa_psk", master, iface->name); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Dup wpa_psk failed"); return -1; } } if (fst_cfgmgr_get_iface_pairwise_cipher(iface, buf, sizeof(buf)-1) > 0) { ret = do_simple_command("IFNAME=%s SET rsn_pairwise %s", iface->name, buf); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set rsn_pairwise failed"); return -1; } } else { ret = do_simple_command("DUP_NETWORK %s %s rsn_pairwise", master, iface->name); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Dup rsn_pairwise failed"); return -1; } } return 0; } static int fst_dup_ap_handle_config(const char *master, const struct fst_iface_info *iface, const char *name, const char *val) { if (!os_strcmp(name, "key_mgmt")) { /* key_mgmt returned from GET_CONFIG means it is a secured AP */ if (!os_strstr(val, "WPA-PSK")) { fst_mgr_printf(MSG_ERROR, "key_mgmt=%s is not supported", val); return -1; } if (do_simple_command("IFNAME=%s SET wpa_key_mgmt WPA-PSK", iface->name) < 0) { fst_mgr_printf(MSG_ERROR, "Set wpa_key_mgmt failed"); return -1; } return fst_dup_ap_wpa_psk(master, iface); } else if (!os_strcmp(name, "ssid") || !os_strcmp(name, "bssid")) { if (do_simple_command("IFNAME=%s SET %s %s", iface->name, name, val) < 0) { fst_mgr_printf(MSG_ERROR, "Set %s failed", name); return -1; } } return 0; } static int fst_ap_update_acl_file(const struct fst_iface_info *iface, const char *acl_file) { return do_simple_command("IFNAME=%s SET accept_mac_file %s", iface->name, acl_file); } static int fst_dup_ap_config(const char *master, const struct fst_iface_info *iface, const char *acl_file) { char buf[2048]; char cmd[256]; char *strstart, *strend, *tok; size_t buf_len = sizeof(buf) - 1; int ret; ret = snprintf(cmd, sizeof(cmd), "IFNAME=%s GET_CONFIG", master); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) goto error_getconfig; strstart = buf; strend = os_strchr(strstart, '\n'); while (strend) { *strend = '\0'; tok = os_strchr(strstart, '='); if (tok) { *tok++ = '\0'; if (fst_dup_ap_handle_config(master, iface, strstart, tok)) goto error_setconfig; } strstart = strend + 1; strend = os_strchr(strstart, '\n'); } if (fst_cfgmgr_get_iface_hw_mode(iface, buf, sizeof(buf) - 1) > 0) { ret = do_simple_command("IFNAME=%s SET hw_mode %s", iface->name, buf); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set hw_mode failed"); goto error_setconfig; } } if (fst_cfgmgr_get_iface_channel(iface, buf, sizeof(buf) - 1) > 0) { ret = do_simple_command("IFNAME=%s SET channel %s", iface->name, buf); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set channel failed"); goto error_setconfig; } } if (acl_file) { ret = do_simple_command("IFNAME=%s SET macaddr_acl 1", iface->name); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set macaddr_acl failed"); goto error_setconfig; } ret = fst_ap_update_acl_file(iface, acl_file); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Update acl file failed"); goto error_setconfig; } } ret = do_simple_command("IFNAME=%s ENABLE", iface->name); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Enabling AP failed"); goto error_setconfig; } return 0; error_setconfig: error_getconfig: return -1; } int fst_add_iface(const char *master, const struct fst_iface_info *iface, const char *acl_file, const char *ctrl_interface) { int res; if (fst_is_supplicant()) { /* INTERFACE_ADD TABTABTAB */ res = do_command_ex(NULL, NULL, "INTERFACE_ADD", " %s\t\t\t%s", iface->name, ctrl_interface ? ctrl_interface : DEFAULT_WPAS_CLI_DIR); } else { res = do_command_ex(NULL, NULL, "ADD", " %s %s", iface->name, ctrl_interface ? ctrl_interface : DEFAULT_HAPD_CLI_DIR); if (res < 0) { fst_mgr_printf(MSG_ERROR, "Failed add AP iface %s", iface->name); return -1; } res = fst_dup_ap_config(master, iface, acl_file); if (res < 0) { fst_mgr_printf(MSG_ERROR, "fst_dup_ap failed"); fst_del_iface(iface); } } return res; } int fst_del_iface(const struct fst_iface_info *iface) { int res; if (fst_is_supplicant()) { res = do_command_ex(NULL, NULL, "INTERFACE_REMOVE", " %s", iface->name); } else { res = do_command_ex(NULL, NULL, "REMOVE", " %s", iface->name); } return res; } static int fst_dedup_station(const struct fst_iface_info *iface) { return do_command_ex(NULL, NULL, "IFNAME=", "%s REMOVE_NETWORK ALL", iface->name); } static int fst_dup_station(const char *master, const struct fst_iface_info *iface, const u8 *bssid, u32 freq) { char buf[4096]; char cmd[256]; char *strstart, *strend; const char *pos; char key_mgmt[10] = "NONE"; size_t buf_len = sizeof(buf) - 1; int ret, srcnetid=-1, netid=-1; ret = snprintf(cmd, sizeof(cmd), "IFNAME=%s LIST_NETWORKS", master); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) goto error_master_status; strstart = buf; strend = os_strchr(strstart, '\n'); while (strend) { *strend = '\0'; if (os_strstr(strstart, "CURRENT")) { srcnetid = (int)strtoul(strstart, NULL, 0); break; } strstart = strend + 1; strend = os_strchr(strstart, '\n'); } if (srcnetid == -1) { fst_mgr_printf(MSG_ERROR, "Cannot find master %s network id", master); goto error_no_netid; } buf_len = sizeof(buf) - 1; ret = snprintf(cmd, sizeof(cmd), "IFNAME=%s ADD_NETWORK", iface->name); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) { goto error_add_network; } if (!strncmp(buf, "FAIL", 4)) { fst_mgr_printf(MSG_ERROR, "Failed add_network for %s: %s", iface->name, buf); goto error_add_network; } netid = (int)strtoul(buf, NULL, 0); fst_mgr_printf(MSG_DEBUG, "Added network %d for %s", netid, iface->name); ret = do_simple_command("IFNAME=%s SET_NETWORK %d bssid " MACSTR, iface->name, netid, MAC2STR(bssid)); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set bssid for %d failed", netid); goto error_set; } buf_len = sizeof(buf) - 1; ret = snprintf(cmd, sizeof(cmd), "IFNAME=%s GET_NETWORK %d key_mgmt", master, srcnetid); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) { goto error_set; } strstart = os_strchr(buf, '\n'); if (!strstart) strstart = buf; else strstart++; fst_mgr_printf(MSG_DEBUG, "key_mgmt for net %d is %s", srcnetid, strstart); if ((pos = os_strstr(strstart, "WPA-PSK")) && ((pos[os_strlen("WPA-PSK")] == ' ') || (pos[os_strlen("WPA-PSK")] == '\0'))) { os_strlcpy(key_mgmt, "WPA-PSK", sizeof(key_mgmt)); } else if ((pos = os_strstr(strstart, "WPA2-PSK")) && ((pos[os_strlen("WPA2-PSK")] == ' ') || (pos[os_strlen("WPA2-PSK")] == '\0'))) { os_strlcpy(key_mgmt, "WPA2-PSK", sizeof(key_mgmt)); } else if (!((pos = os_strstr(strstart, "NONE")))) { fst_mgr_printf(MSG_ERROR, "Unsupported key_mgmt, %s", strstart); goto error_set; } fst_mgr_printf(MSG_DEBUG, "updated key_mgmt for net %d is %s", srcnetid, key_mgmt); ret = do_simple_command("IFNAME=%s SET_NETWORK %d key_mgmt %s", iface->name, netid, key_mgmt); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set wpa key_mgmt for %d failed", netid); goto error_set; } if (freq != 0) { ret = do_simple_command("IFNAME=%s SET_NETWORK %d scan_freq %d", iface->name, netid, freq); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set frequency for %d failed", netid); goto error_set; } } if (os_strcmp(key_mgmt, "NONE")) { /* The target network is WPA-PSK */ strstart = buf; if (fst_cfgmgr_get_iface_group_cipher(iface, buf, sizeof(buf) - 1) == 0) { buf_len = sizeof(buf) - 1; ret = snprintf(cmd, sizeof(cmd), "IFNAME=%s GET_NETWORK %d group", master, srcnetid); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) { goto error_set; } strstart = os_strchr(buf, '\n'); if (!strstart) strstart = buf; else strstart++; } ret = do_simple_command("IFNAME=%s SET_NETWORK %d group %s", iface->name, netid, strstart); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set group for %d failed", netid); goto error_set; } strstart = buf; if (fst_cfgmgr_get_iface_pairwise_cipher(iface, buf, sizeof(buf) - 1) == 0) { buf_len = sizeof(buf) - 1; ret = snprintf(cmd, sizeof(cmd), "IFNAME=%s GET_NETWORK %d pairwise", master, srcnetid); ret = do_hostap_command(cmd, ret, buf, &buf_len); if (ret < 0) { goto error_set; } strstart = os_strchr(buf, '\n'); if (!strstart) strstart = buf; else strstart++; } ret = do_simple_command("IFNAME=%s SET_NETWORK %d pairwise %s", iface->name, netid, strstart); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Set pairwise for %d failed", netid); goto error_set; } ret = do_simple_command("DUP_NETWORK %s %s %d %d psk", master, iface->name, srcnetid, netid); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Dup PSK for %d failed", netid); goto error_set; } } do_command_ex(NULL, NULL, "IFNAME=", "%s SET_NETWORK %d enable_edmg 1", iface->name, netid); do_command_ex(NULL, NULL, "IFNAME=", "%s SCAN_INTERVAL %d", iface->name, scan_interval); ret = do_simple_command("IFNAME=%s SELECT_NETWORK %d", iface->name, netid); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "Select network for %d failed", netid); goto error_set; } return 0; error_set: if (netid != -1) fst_dedup_station(iface); error_add_network: error_no_netid: error_master_status: return -1; } int fst_dup_connection(const struct fst_iface_info *iface, const char *master, const u8 *addr, u32 freq, const char *acl_file) { int res = 0; if ( fst_is_supplicant()) { res = fst_dup_station(master, iface, addr, freq); } else { if (acl_file && fst_ap_update_acl_file(iface, acl_file) < 0) fst_mgr_printf(MSG_WARNING, "cannot update ACL file for %s", iface->name); } return res; } int fst_dedup_connection(const struct fst_iface_info *iface, const char *acl_file) { int res = 0; if ( fst_is_supplicant()) { res = fst_dedup_station(iface); } else { if (acl_file && fst_ap_update_acl_file(iface, acl_file) < 0) fst_mgr_printf(MSG_WARNING, "cannot update ACL file for %s", iface->name); } return res; } int fst_disconnect_peer(const char *ifname, const u8 *peer_addr) { int ret = -1; if (fst_is_supplicant()) { fst_mgr_printf(MSG_INFO, "ifname=%s", ifname); ret = do_simple_command("IFNAME=%s DISCONNECT", ifname); } else if (peer_addr) { fst_mgr_printf(MSG_INFO, "ifname=%s, peer=" MACSTR, ifname, MAC2STR(peer_addr)); ret = do_simple_command("IFNAME=%s DISASSOCIATE " MACSTR, ifname, MAC2STR(peer_addr)); } if (ret < 0) { fst_mgr_printf(MSG_ERROR, "failed to disconnect peer"); return ret; } return 0; } void fst_free(void *p) { free(p); } /* wpa ctrl */ static void fst_ctrl_receiver(int sock, void *eloop_ctx, void *sock_ctx) { char buf[4096]; struct wpa_ctrl *ctrl = sock_ctx; size_t len; while (wpa_ctrl_pending(ctrl) > 0) { len = sizeof(buf) - 1; if (wpa_ctrl_recv(ctrl, buf, &len)) { fst_mgr_printf(MSG_ERROR, "wpa_ctrl_recv"); break; } buf[len] = '\0'; if (fst_ctrl_notify(buf, len)) { fst_mgr_printf(MSG_ERROR, "fst_ctrl_notify"); goto cli_disconnected; } } if (wpa_ctrl_pending(ctrl) < 0) { fst_mgr_printf(MSG_ERROR, "wpa_ctrl_pending: connection lost: " "terminating"); goto cli_disconnected; } return; cli_disconnected: eloop_terminate(); } static struct wpa_ctrl *try_to_open_wpa_ctrl(const char *name) { WPA_ASSERT(name != NULL); if (name && *name) { #ifndef ANDROID struct stat stat_buf; if (stat(name, &stat_buf) < 0) { fst_mgr_printf(MSG_ERROR, "cannot stat \'%s\'", name); return NULL; } #endif return wpa_ctrl_open(name); } return NULL; } static void fst_ping(void *eloop_ctx, void *timeout_ctx) { char buf[4096]; size_t buf_len = sizeof(buf) - 1; int ret; fst_mgr_printf(MSG_EXCESSIVE, "send: PING"); ret = wpa_ctrl_request(ctrl_cmd, "PING", 4, buf, &buf_len, NULL); if (ret < 0) { fst_mgr_printf(MSG_ERROR, "PING %s", ret == -2 ? "timed out" : "failed"); goto error_ping; } buf[buf_len] = '\0'; if (os_strncmp(buf, "PONG", 4)) { fst_mgr_printf(MSG_ERROR, "Wrong PING response: %s", buf); goto error_pong; } eloop_register_timeout(ctrl_ping_interval, 0, fst_ping, NULL, NULL); return; error_ping: error_pong: eloop_terminate(); } Boolean fst_ctrl_create(const char *ctrl_iface, unsigned int ping_interval) { char buf[64] = { '\0' }; WPA_ASSERT(ctrl_iface != NULL); WPA_ASSERT(ctrl_evt == NULL); WPA_ASSERT(ctrl_cmd == NULL); if (ctrl_iface == NULL) { fst_mgr_printf(MSG_ERROR, "ctrl_iface is invalid"); return FALSE; } ctrl_evt = try_to_open_wpa_ctrl(ctrl_iface); if (!ctrl_evt) { fst_mgr_printf(MSG_ERROR, "cannot open control iface (evt): %s", ctrl_iface); goto error_open_evt; } if (wpa_ctrl_attach(ctrl_evt)) { fst_mgr_printf(MSG_ERROR, "cannot attach control iface: %s", ctrl_iface); goto error_attach_evt; } ctrl_cmd = try_to_open_wpa_ctrl(ctrl_iface); if (!ctrl_cmd) { fst_mgr_printf(MSG_ERROR, "cannot open control iface (cmd): %s", ctrl_iface); goto error_open_cmd; } if (eloop_register_read_sock(wpa_ctrl_get_fd(ctrl_evt), fst_ctrl_receiver, NULL, ctrl_evt)) { fst_mgr_printf(MSG_ERROR, "eloop_register_read_sock"); goto error_eloop_register_read_sock; } if (fst_detect_ctrl_type()) { fst_mgr_printf(MSG_ERROR, "cannot detect CTRL type"); goto error_detect_cli_type; } ctrl_ping_interval = ping_interval; if (ctrl_ping_interval) eloop_register_timeout(ctrl_ping_interval, 0, fst_ping, NULL, NULL); fst_get_config_string(FST_CONFIG_SCAN_INTERVAL_KEY_NAME, FST_CONFIG_SCAN_INTERVAL, buf, sizeof(buf)); scan_interval = strtoul(buf, NULL, 0); fst_mgr_printf(MSG_INFO, "connected to control iface: %s", ctrl_iface); return TRUE; error_detect_cli_type: eloop_unregister_read_sock(wpa_ctrl_get_fd(ctrl_evt)); error_eloop_register_read_sock: wpa_ctrl_close(ctrl_cmd); ctrl_cmd = NULL; error_open_cmd: wpa_ctrl_detach(ctrl_evt); error_attach_evt: wpa_ctrl_close(ctrl_evt); ctrl_evt = NULL; error_open_evt: return FALSE; } void fst_ctrl_free(void) { WPA_ASSERT(ctrl_evt != NULL); WPA_ASSERT(ctrl_cmd != NULL); if (ctrl_evt != NULL) eloop_unregister_read_sock(wpa_ctrl_get_fd(ctrl_evt)); eloop_cancel_timeout(fst_ping, NULL, NULL); if (ctrl_cmd != NULL) { wpa_ctrl_close(ctrl_cmd); ctrl_cmd = NULL; } if (ctrl_evt != NULL) { wpa_ctrl_detach(ctrl_evt); wpa_ctrl_close(ctrl_evt); ctrl_evt = NULL; } } int fst_signal_monitor(const char *iface, int thresh, int hyst) { return do_simple_command("IFNAME=%s SIGNAL_MONITOR THRESHOLD=%d HYSTERESIS=%d", iface, thresh, hyst); } void fst_ctrl_bss_flush(const char *iface) { do_command_ex(NULL, NULL, "IFNAME=", "%s BSS_FLUSH 0", iface); }