/* * * Connection Manager * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, 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 #include #include #define CONNMAN_API_SUBJECT_TO_CHANGE #include #include #include #include #include #define TIMEOUT 30000 #define NEARD_SERVICE "org.neard" #define NEARD_PATH "/" #define NEARD_MANAGER_INTERFACE "org.neard.Manager" #define NEARD_AGENT_INTERFACE "org.neard.HandoverAgent" #define NEARD_ERROR_INTERFACE "org.neard.HandoverAgent.Error" #define AGENT_PATH "/net/connman/neard_handover_agent" #define AGENT_TYPE "wifi" #define NEARD_SERVICE_GROUP "service_neard_provision" /* Configuration keys */ #define SERVICE_KEY_TYPE "Type" #define SERVICE_KEY_SSID "SSID" #define SERVICE_KEY_PASSPHRASE "Passphrase" #define SERVICE_KEY_HIDDEN "Hidden" struct data_elements { unsigned int value; unsigned int length; bool fixed_length; }; typedef enum { DE_AUTHENTICATION_TYPE = 0, DE_NETWORK_KEY = 1, DE_SSID = 2, DE_MAX = 3, } DEid; static const struct data_elements DEs[DE_MAX] = { { 0x1003, 2, TRUE }, { 0x1027, 64, FALSE }, { 0x1045, 32, FALSE }, }; #define DE_VAL_OPEN 0x0001 #define DE_VAL_PSK 0x0022 struct wifi_sc { gchar *ssid; gchar *passphrase; }; static DBusConnection *connection = NULL; DBusPendingCall *register_call = NULL; bool agent_registered = false; static guint watch_id = 0; static int set_2b_into_tlv(uint8_t *tlv_msg, uint16_t val) { uint16_t ne_val; uint8_t *ins; ins = (uint8_t *) &ne_val; ne_val = htons(val); tlv_msg[0] = ins[0]; tlv_msg[1] = ins[1]; return 2; } static int set_byte_array_into_tlv(uint8_t *tlv_msg, uint8_t *array, int length) { memcpy((void *)tlv_msg, (void *)array, length); return length; } static uint8_t *encode_to_tlv(const char *ssid, const char *psk, int *length) { uint16_t ssid_len, psk_len; uint8_t *tlv_msg; int pos = 0; if (!ssid || !length) return NULL; ssid_len = strlen(ssid); *length = 6 + 4 + ssid_len; if (psk) { psk_len = strlen(psk); *length += 4 + psk_len; } else psk_len = 0; tlv_msg = g_try_malloc0(sizeof(uint8_t) * (*length)); if (!tlv_msg) return NULL; pos += set_2b_into_tlv(tlv_msg+pos, DEs[DE_SSID].value); pos += set_2b_into_tlv(tlv_msg+pos, ssid_len); pos += set_byte_array_into_tlv(tlv_msg+pos, (uint8_t *)ssid, ssid_len); pos += set_2b_into_tlv(tlv_msg+pos, DEs[DE_AUTHENTICATION_TYPE].value); pos += set_2b_into_tlv(tlv_msg+pos, DEs[DE_AUTHENTICATION_TYPE].length); if (psk) { pos += set_2b_into_tlv(tlv_msg+pos, DE_VAL_PSK); pos += set_2b_into_tlv(tlv_msg+pos, DEs[DE_NETWORK_KEY].value); pos += set_2b_into_tlv(tlv_msg+pos, psk_len); pos += set_byte_array_into_tlv(tlv_msg+pos, (uint8_t *)psk, psk_len); } else pos += set_2b_into_tlv(tlv_msg+pos, DE_VAL_OPEN); return tlv_msg; } static DBusMessage *get_reply_on_error(DBusMessage *message, int error) { if (error == ENOTSUP) return g_dbus_create_error(message, NEARD_ERROR_INTERFACE ".NotSupported", "Operation is not supported"); return g_dbus_create_error(message, NEARD_ERROR_INTERFACE ".Failed", "Invalid parameters"); } static int parse_request_oob_params(DBusMessage *message, const uint8_t **tlv_msg, int *length) { DBusMessageIter iter; DBusMessageIter array; DBusMessageIter dict_entry; const char *key; int arg_type; if (!tlv_msg || !length) return -EINVAL; dbus_message_iter_init(message, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&iter, &array); arg_type = dbus_message_iter_get_arg_type(&array); if (arg_type != DBUS_TYPE_DICT_ENTRY) return -EINVAL; while (arg_type != DBUS_TYPE_INVALID) { dbus_message_iter_recurse(&array, &dict_entry); if (dbus_message_iter_get_arg_type(&dict_entry) != DBUS_TYPE_STRING) return -EINVAL; dbus_message_iter_get_basic(&dict_entry, &key); if (g_strcmp0(key, "WSC") == 0) { DBusMessageIter value; DBusMessageIter fixed_array; dbus_message_iter_next(&dict_entry); dbus_message_iter_recurse(&dict_entry, &value); if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &fixed_array); dbus_message_iter_get_fixed_array(&fixed_array, tlv_msg, length); return 0; } dbus_message_iter_next(&array); arg_type = dbus_message_iter_get_arg_type(&array); } return -EINVAL; } static DBusMessage *create_request_oob_reply(DBusMessage *message) { DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; const char *ssid, *psk; uint8_t *tlv_msg; int length; if (!connman_technology_get_wifi_tethering(&ssid, &psk)) return get_reply_on_error(message, ENOTSUP); tlv_msg = encode_to_tlv(ssid, psk, &length); if (!tlv_msg) return get_reply_on_error(message, ENOTSUP); reply = dbus_message_new_method_return(message); if (!reply) goto out; dbus_message_iter_init_append(reply, &iter); connman_dbus_dict_open(&iter, &dict); connman_dbus_dict_append_fixed_array(&dict, "WSC", DBUS_TYPE_BYTE, &tlv_msg, length); dbus_message_iter_close_container(&iter, &dict); out: g_free(tlv_msg); return reply; } static DBusMessage *request_oob_method(DBusConnection *dbus_conn, DBusMessage *message, void *user_data) { DBusMessageIter iter; DBG(""); dbus_message_iter_init(message, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return get_reply_on_error(message, EINVAL); return create_request_oob_reply(message); } static void free_wifi_sc(struct wifi_sc *wsc) { if (!wsc) return; g_free(wsc->ssid); g_free(wsc->passphrase); g_free(wsc); } static inline int get_2b_from_tlv(const uint8_t *tlv_msg, uint16_t *val) { *val = ntohs(tlv_msg[0] | (tlv_msg[1] << 8)); return 2; } static uint8_t *get_byte_array_from_tlv(const uint8_t *tlv_msg, int length) { uint8_t *array; array = g_try_malloc0(sizeof(uint8_t) * length); if (!array) return NULL; memcpy((void *)array, (void *)tlv_msg, length*sizeof(uint8_t)); return array; } static char *get_string_from_tlv(const uint8_t *tlv_msg, int length) { char *str; str = g_try_malloc0((sizeof(char) * length) + 1); if (!str) return NULL; memcpy((void *)str, (void *)tlv_msg, length*sizeof(char)); return str; } static inline DEid get_de_id(uint16_t attr) { int i; for (i = 0; i < DE_MAX && DEs[i].value != 0; i++) { if (attr == DEs[i].value) return i; } return DE_MAX; } static inline bool is_de_length_fine(DEid id, uint16_t length) { if (DEs[id].fixed_length) return (length == DEs[id].length); return (length <= DEs[id].length); } static char *get_hexstr_from_byte_array(const uint8_t *bt, int length) { char *hex_str; int i, j; hex_str = g_try_malloc0(((length*2)+1) * sizeof(char)); if (!hex_str) return NULL; for (i = 0, j = 0; i < length; i++, j += 2) snprintf(hex_str+j, 3, "%02x", bt[i]); return hex_str; } static struct wifi_sc *decode_from_tlv(const uint8_t *tlv_msg, int length) { struct wifi_sc *wsc; uint16_t attr, len; uint8_t *bt; int pos; DEid id; if (!tlv_msg || length == 0) return NULL; wsc = g_try_malloc0(sizeof(struct wifi_sc)); if (!wsc) return NULL; pos = 0; while (pos < length) { pos += get_2b_from_tlv(tlv_msg+pos, &attr); pos += get_2b_from_tlv(tlv_msg+pos, &len); if (len > length - pos) goto error; id = get_de_id(attr); if (id == DE_MAX) { pos += len; continue; } if (!is_de_length_fine(id, len)) goto error; switch (id) { case DE_AUTHENTICATION_TYPE: break; case DE_NETWORK_KEY: wsc->passphrase = get_string_from_tlv(tlv_msg+pos, len); break; case DE_SSID: bt = get_byte_array_from_tlv(tlv_msg+pos, len); wsc->ssid = get_hexstr_from_byte_array(bt, len); g_free(bt); break; case DE_MAX: /* Falling back. Should never happen though. */ default: break; } pos += len; } return wsc; error: free_wifi_sc(wsc); return NULL; } static int handle_wcs_data(const uint8_t *tlv_msg, int length) { struct wifi_sc *wsc = NULL; GKeyFile *keyfile = NULL; int ret = -EINVAL; wsc = decode_from_tlv(tlv_msg, length); if (!wsc) return -EINVAL; if (!wsc->ssid) goto out; keyfile = g_key_file_new(); if (!keyfile) { ret = -ENOMEM; goto out; } g_key_file_set_string(keyfile, NEARD_SERVICE_GROUP, SERVICE_KEY_TYPE, "wifi"); g_key_file_set_string(keyfile, NEARD_SERVICE_GROUP, SERVICE_KEY_SSID, wsc->ssid); g_key_file_set_boolean(keyfile, NEARD_SERVICE_GROUP, SERVICE_KEY_HIDDEN, TRUE); if (wsc->passphrase) g_key_file_set_string(keyfile, NEARD_SERVICE_GROUP, SERVICE_KEY_PASSPHRASE, wsc->passphrase); ret = connman_config_provision_mutable_service(keyfile); out: g_key_file_free(keyfile); free_wifi_sc(wsc); return ret; } static DBusMessage *push_oob_method(DBusConnection *dbus_conn, DBusMessage *message, void *user_data) { const uint8_t *tlv_msg; int err = EINVAL; int length; DBG(""); if (parse_request_oob_params(message, &tlv_msg, &length) != 0) return get_reply_on_error(message, err); err = handle_wcs_data(tlv_msg, length); if (err != 0) return get_reply_on_error(message, err); return g_dbus_create_reply(message, DBUS_TYPE_INVALID); } static DBusMessage *release_method(DBusConnection *dbus_conn, DBusMessage *message, void *user_data) { DBG(""); agent_registered = false; g_dbus_unregister_interface(connection, AGENT_PATH, NEARD_AGENT_INTERFACE); return g_dbus_create_reply(message, DBUS_TYPE_INVALID); } static const GDBusMethodTable neard_methods[] = { { GDBUS_ASYNC_METHOD("RequestOOB", GDBUS_ARGS({ "data", "a{sv}" }), GDBUS_ARGS({ "data", "a{sv}" }), request_oob_method) }, { GDBUS_ASYNC_METHOD("PushOOB", GDBUS_ARGS({ "data", "a{sv}"}), NULL, push_oob_method) }, { GDBUS_METHOD("Release", NULL, NULL, release_method) }, { }, }; static void cleanup_register_call(void) { if (register_call) { dbus_pending_call_cancel(register_call); dbus_pending_call_unref(register_call); register_call = NULL; } } static void register_agent_cb(DBusPendingCall *pending, void *user_data) { DBusMessage *reply; if (!dbus_pending_call_get_completed(pending)) return; register_call = NULL; reply = dbus_pending_call_steal_reply(pending); if (!reply) goto out; if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { g_dbus_unregister_interface(connection, AGENT_PATH, NEARD_AGENT_INTERFACE); } else agent_registered = true; dbus_message_unref(reply); out: dbus_pending_call_unref(pending); } static void register_agent(void) { const char *path = AGENT_PATH; const char *type = AGENT_TYPE; DBusMessage *message; message = dbus_message_new_method_call(NEARD_SERVICE, NEARD_PATH, NEARD_MANAGER_INTERFACE, "RegisterHandoverAgent"); if (!message) return; dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &type, DBUS_TYPE_INVALID); if (!dbus_connection_send_with_reply(connection, message, ®ister_call, TIMEOUT)) { dbus_message_unref(message); goto out; } if (!dbus_pending_call_set_notify(register_call, register_agent_cb, NULL, NULL)) cleanup_register_call(); out: dbus_message_unref(message); } static void unregister_agent(void) { const char *path = AGENT_PATH; const char *type = AGENT_TYPE; DBusMessage *message; if (!agent_registered) return cleanup_register_call(); agent_registered = false; message = dbus_message_new_method_call(NEARD_SERVICE, NEARD_PATH, NEARD_MANAGER_INTERFACE, "UnregisterHandoverAgent"); if (message) { dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &type, DBUS_TYPE_INVALID); g_dbus_send_message(connection, message); } g_dbus_unregister_interface(connection, AGENT_PATH, NEARD_AGENT_INTERFACE); } static void neard_is_present(DBusConnection *conn, void *user_data) { DBG(""); if (agent_registered) return; if (g_dbus_register_interface(connection, AGENT_PATH, NEARD_AGENT_INTERFACE, neard_methods, NULL, NULL, NULL, NULL)) register_agent(); } static void neard_is_out(DBusConnection *conn, void *user_data) { DBG(""); if (agent_registered) { g_dbus_unregister_interface(connection, AGENT_PATH, NEARD_AGENT_INTERFACE); agent_registered = false; } cleanup_register_call(); } static int neard_init(void) { connection = connman_dbus_get_connection(); if (!connection) return -EIO; watch_id = g_dbus_add_service_watch(connection, NEARD_SERVICE, neard_is_present, neard_is_out, NULL, NULL); if (watch_id == 0) { dbus_connection_unref(connection); return -ENOMEM; } return 0; } static void neard_exit(void) { unregister_agent(); if (watch_id != 0) g_dbus_remove_watch(connection, watch_id); if (connection) dbus_connection_unref(connection); } CONNMAN_PLUGIN_DEFINE(neard, "Neard handover plugin", VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT, neard_init, neard_exit)