diff options
Diffstat (limited to 'dom/wifi/WifiUtils.cpp')
-rw-r--r-- | dom/wifi/WifiUtils.cpp | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/dom/wifi/WifiUtils.cpp b/dom/wifi/WifiUtils.cpp new file mode 100644 index 000000000..2526c9ea4 --- /dev/null +++ b/dom/wifi/WifiUtils.cpp @@ -0,0 +1,521 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WifiUtils.h" +#include <dlfcn.h> +#include <errno.h> +#include <cutils/properties.h> +#include "prinit.h" +#include "mozilla/Sprintf.h" +#include "js/CharacterEncoding.h" + +using namespace mozilla::dom; + +#define BUFFER_SIZE 4096 +#define COMMAND_SIZE 256 + +// Intentionally not trying to dlclose() this handle. That's playing +// Russian roulette with security bugs. +static void* sWifiLib; +static PRCallOnceType sInitWifiLib; + +static PRStatus +InitWifiLib() +{ + sWifiLib = dlopen("/system/lib/libhardware_legacy.so", RTLD_LAZY); + // We might fail to open the hardware lib. That's OK. + return PR_SUCCESS; +} + +static void* +GetSharedLibrary() +{ + PR_CallOnce(&sInitWifiLib, InitWifiLib); + return sWifiLib; +} + +static bool +GetWifiP2pSupported() +{ + char propP2pSupported[PROPERTY_VALUE_MAX]; + property_get("ro.moz.wifi.p2p_supported", propP2pSupported, "0"); + return (0 == strcmp(propP2pSupported, "1")); +} + +static int +hex2num(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +static int +hex2byte(const char* hex) +{ + int a, b; + a = hex2num(*hex++); + if (a < 0) + return -1; + b = hex2num(*hex++); + if (b < 0) + return -1; + return (a << 4) | b; +} + +// This function is equivalent to printf_decode() at src/utils/common.c in +// the supplicant. + +static uint32_t +convertToBytes(char* buf, uint32_t maxlen, const char* str) +{ + const char *pos = str; + uint32_t len = 0; + int val; + + while (*pos) { + if (len == maxlen) + break; + switch (*pos) { + case '\\': + pos++; + switch (*pos) { + case '\\': + buf[len++] = '\\'; + pos++; + break; + case '"': + buf[len++] = '"'; + pos++; + break; + case 'n': + buf[len++] = '\n'; + pos++; + break; + case 'r': + buf[len++] = '\r'; + pos++; + break; + case 't': + buf[len++] = '\t'; + pos++; + break; + case 'e': + buf[len++] = '\e'; + pos++; + break; + case 'x': + pos++; + val = hex2byte(pos); + if (val < 0) { + val = hex2num(*pos); + if (val < 0) + break; + buf[len++] = val; + pos++; + } else { + buf[len++] = val; + pos += 2; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + val = *pos++ - '0'; + if (*pos >= '0' && *pos <= '7') + val = val * 8 + (*pos++ - '0'); + if (*pos >= '0' && *pos <= '7') + val = val * 8 + (*pos++ - '0'); + buf[len++] = val; + break; + default: + break; + } + break; + default: + buf[len++] = *pos++; + break; + } + } + return len; +} + +// This is the same algorithm as in InflateUTF8StringToBuffer with Copy and +// while ignoring invalids. +// https://mxr.mozilla.org/mozilla-central/source/js/src/vm/CharacterEncoding.cpp#231 + +static const uint32_t REPLACE_UTF8 = 0xFFFD; + +static void +LossyConvertUTF8toUTF16(const char* aInput, uint32_t aLength, nsAString& aOut) +{ + JS::UTF8Chars src(aInput, aLength); + + char16_t dst[aLength]; // Allocating for worst case. + + // Count how many char16_t characters are needed in the inflated string. + // |i| is the index into |src|, and |j| is the the index into |dst|. + size_t srclen = src.length(); + uint32_t j = 0; + for (uint32_t i = 0; i < srclen; i++, j++) { + uint32_t v = uint32_t(src[i]); + if (v == uint32_t('\0') && i < srclen - 1) { + // If the leading byte is '\0' and it's not the last byte, + // just ignore it to prevent from being truncated. This could + // be caused by |convertToBytes| (e.g. \x00 would be converted to '\0') + j--; + continue; + } + if (!(v & 0x80)) { + // ASCII code unit. Simple copy. + dst[j] = char16_t(v); + } else { + // Non-ASCII code unit. Determine its length in bytes (n). + uint32_t n = 1; + while (v & (0x80 >> n)) + n++; + + #define INVALID(report, arg, n2) \ + do { \ + n = n2; \ + goto invalidMultiByteCodeUnit; \ + } while (0) + + // Check the leading byte. + if (n < 2 || n > 4) + INVALID(ReportInvalidCharacter, i, 1); + + // Check that |src| is large enough to hold an n-byte code unit. + if (i + n > srclen) + INVALID(ReportBufferTooSmall, /* dummy = */ 0, 1); + + // Check the second byte. From Unicode Standard v6.2, Table 3-7 + // Well-Formed UTF-8 Byte Sequences. + if ((v == 0xE0 && ((uint8_t)src[i + 1] & 0xE0) != 0xA0) || // E0 A0~BF + (v == 0xED && ((uint8_t)src[i + 1] & 0xE0) != 0x80) || // ED 80~9F + (v == 0xF0 && ((uint8_t)src[i + 1] & 0xF0) == 0x80) || // F0 90~BF + (v == 0xF4 && ((uint8_t)src[i + 1] & 0xF0) != 0x80)) // F4 80~8F + { + INVALID(ReportInvalidCharacter, i, 1); + } + + // Check the continuation bytes. + for (uint32_t m = 1; m < n; m++) + if ((src[i + m] & 0xC0) != 0x80) + INVALID(ReportInvalidCharacter, i, m); + + // Determine the code unit's length in char16_t units and act accordingly. + v = JS::Utf8ToOneUcs4Char((uint8_t *)&src[i], n); + if (v < 0x10000) { + // The n-byte UTF8 code unit will fit in a single char16_t. + dst[j] = char16_t(v); + } else { + v -= 0x10000; + if (v <= 0xFFFFF) { + // The n-byte UTF8 code unit will fit in two char16_t units. + dst[j] = char16_t((v >> 10) + 0xD800); + j++; + dst[j] = char16_t((v & 0x3FF) + 0xDC00); + } else { + // The n-byte UTF8 code unit won't fit in two char16_t units. + INVALID(ReportTooBigCharacter, v, 1); + } + } + + invalidMultiByteCodeUnit: + // Move i to the last byte of the multi-byte code unit; the loop + // header will do the final i++ to move to the start of the next + // code unit. + i += n - 1; + } + } + + dst[j] = 0; + aOut = dst; +} + +// Helper to check we have loaded the hardware shared library. +#define CHECK_HWLIB(ret) \ + void* hwLib = GetSharedLibrary(); \ + if (!hwLib) { \ + NS_WARNING("No /system/lib/libhardware_legacy.so"); \ + return ret; \ + } + +#define DEFAULT_IMPL(name, ret, args...) \ + DEFINE_DLFUNC(name, ret, args...) \ + ret do_##name(args) { \ + USE_DLFUNC(name) \ + return name(args); \ + } + +// ICS implementation. +class ICSWpaSupplicantImpl : public WpaSupplicantImpl +{ +public: + DEFAULT_IMPL(wifi_load_driver, int32_t, ) + DEFAULT_IMPL(wifi_unload_driver, int32_t, ) + + DEFINE_DLFUNC(wifi_wait_for_event, int32_t, char*, size_t) + int32_t do_wifi_wait_for_event(const char *iface, char *buf, size_t len) { + USE_DLFUNC(wifi_wait_for_event) + return wifi_wait_for_event(buf, len); + } + + DEFINE_DLFUNC(wifi_command, int32_t, const char*, char*, size_t*) + int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) { + USE_DLFUNC(wifi_command) + return wifi_command(cmd, buf, len); + } + + DEFINE_DLFUNC(wifi_start_supplicant, int32_t, ) + int32_t do_wifi_start_supplicant(int32_t) { + USE_DLFUNC(wifi_start_supplicant) + return wifi_start_supplicant(); + } + + DEFINE_DLFUNC(wifi_stop_supplicant, int32_t) + int32_t do_wifi_stop_supplicant(int32_t) { + USE_DLFUNC(wifi_stop_supplicant) + return wifi_stop_supplicant(); + } + + DEFINE_DLFUNC(wifi_connect_to_supplicant, int32_t, ) + int32_t do_wifi_connect_to_supplicant(const char* iface) { + USE_DLFUNC(wifi_connect_to_supplicant) + return wifi_connect_to_supplicant(); + } + + DEFINE_DLFUNC(wifi_close_supplicant_connection, void, ) + void do_wifi_close_supplicant_connection(const char* iface) { + USE_DLFUNC(wifi_close_supplicant_connection) + return wifi_close_supplicant_connection(); + } +}; + +// JB implementation. +// We only redefine the methods that have a different signature than on ICS. +class JBWpaSupplicantImpl : public ICSWpaSupplicantImpl +{ +public: + DEFINE_DLFUNC(wifi_wait_for_event, int32_t, const char*, char*, size_t) + int32_t do_wifi_wait_for_event(const char* iface, char* buf, size_t len) { + USE_DLFUNC(wifi_wait_for_event) + return wifi_wait_for_event(iface, buf, len); + } + + DEFINE_DLFUNC(wifi_command, int32_t, const char*, const char*, char*, size_t*) + int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) { + USE_DLFUNC(wifi_command) + return wifi_command(iface, cmd, buf, len); + } + + DEFINE_DLFUNC(wifi_start_supplicant, int32_t, int32_t) + int32_t do_wifi_start_supplicant(int32_t arg) { + USE_DLFUNC(wifi_start_supplicant) + return wifi_start_supplicant(arg); + } + + DEFINE_DLFUNC(wifi_stop_supplicant, int32_t, int32_t) + int32_t do_wifi_stop_supplicant(int32_t arg) { + USE_DLFUNC(wifi_stop_supplicant) + return wifi_stop_supplicant(arg); + } + + DEFINE_DLFUNC(wifi_connect_to_supplicant, int32_t, const char*) + int32_t do_wifi_connect_to_supplicant(const char* iface) { + USE_DLFUNC(wifi_connect_to_supplicant) + return wifi_connect_to_supplicant(iface); + } + + DEFINE_DLFUNC(wifi_close_supplicant_connection, void, const char*) + void do_wifi_close_supplicant_connection(const char* iface) { + USE_DLFUNC(wifi_close_supplicant_connection) + wifi_close_supplicant_connection(iface); + } +}; + +// KK implementation. +// We only redefine the methods that have a different signature than on ICS. +class KKWpaSupplicantImpl : public ICSWpaSupplicantImpl +{ +public: + DEFINE_DLFUNC(wifi_start_supplicant, int32_t, int32_t) + int32_t do_wifi_start_supplicant(int32_t arg) { + USE_DLFUNC(wifi_start_supplicant) + return wifi_start_supplicant(arg); + } + + DEFINE_DLFUNC(wifi_stop_supplicant, int32_t, int32_t) + int32_t do_wifi_stop_supplicant(int32_t arg) { + USE_DLFUNC(wifi_stop_supplicant) + return wifi_stop_supplicant(arg); + } + + DEFINE_DLFUNC(wifi_command, int32_t, const char*, char*, size_t*) + int32_t do_wifi_command(const char* iface, const char* cmd, char* buf, size_t* len) { + char command[COMMAND_SIZE]; + if (!strcmp(iface, "p2p0")) { + // Commands for p2p0 interface don't need prefix + SprintfLiteral(command, "%s", cmd); + } + else { + SprintfLiteral(command, "IFNAME=%s %s", iface, cmd); + } + USE_DLFUNC(wifi_command) + return wifi_command(command, buf, len); + } +}; + +// Concrete class to use to access the wpa supplicant. +WpaSupplicant::WpaSupplicant() +{ + char propVersion[PROPERTY_VALUE_MAX]; + property_get("ro.build.version.sdk", propVersion, "0"); + mSdkVersion = strtol(propVersion, nullptr, 10); + + if (mSdkVersion < 16) { + mImpl = MakeUnique<ICSWpaSupplicantImpl>(); + } else if (mSdkVersion < 19) { + mImpl = MakeUnique<JBWpaSupplicantImpl>(); + } else { + mImpl = MakeUnique<KKWpaSupplicantImpl>(); + } + mWifiHotspotUtils = MakeUnique<WifiHotspotUtils>(); +}; + +void WpaSupplicant::WaitForEvent(nsAString& aEvent, const nsCString& aInterface) +{ + CHECK_HWLIB() + + char buffer[BUFFER_SIZE]; + int32_t ret = mImpl->do_wifi_wait_for_event(aInterface.get(), buffer, BUFFER_SIZE); + CheckBuffer(buffer, ret, aEvent); +} + +#define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aOptions.prop).get() + +/** + * Make a subnet mask. + */ +uint32_t WpaSupplicant::MakeMask(uint32_t len) { + uint32_t mask = 0; + for (uint32_t i = 0; i < len; ++i) { + mask |= (0x80000000 >> i); + } + return ntohl(mask); +} + +bool WpaSupplicant::ExecuteCommand(CommandOptions aOptions, + WifiResultOptions& aResult, + const nsCString& aInterface) +{ + CHECK_HWLIB(false) + + if (!mWifiHotspotUtils->GetSharedLibrary()) { + return false; + } + + // Always correlate the opaque ids. + aResult.mId = aOptions.mId; + + if (aOptions.mCmd.EqualsLiteral("command")) { + size_t len = BUFFER_SIZE - 1; + char buffer[BUFFER_SIZE]; + NS_ConvertUTF16toUTF8 request(aOptions.mRequest); + aResult.mStatus = mImpl->do_wifi_command(aInterface.get(), request.get(), buffer, &len); + nsString value; + if (aResult.mStatus == 0) { + if (buffer[len - 1] == '\n') { // remove trailing new lines. + len--; + } + buffer[len] = '\0'; + CheckBuffer(buffer, len, value); + } + aResult.mReply = value; + } else if (aOptions.mCmd.EqualsLiteral("close_supplicant_connection")) { + mImpl->do_wifi_close_supplicant_connection(aInterface.get()); + } else if (aOptions.mCmd.EqualsLiteral("load_driver")) { + aResult.mStatus = mImpl->do_wifi_load_driver(); + } else if (aOptions.mCmd.EqualsLiteral("unload_driver")) { + aResult.mStatus = mImpl->do_wifi_unload_driver(); + } else if (aOptions.mCmd.EqualsLiteral("start_supplicant")) { + aResult.mStatus = mImpl->do_wifi_start_supplicant(GetWifiP2pSupported() ? 1 : 0); + } else if (aOptions.mCmd.EqualsLiteral("stop_supplicant")) { + aResult.mStatus = mImpl->do_wifi_stop_supplicant(0); + } else if (aOptions.mCmd.EqualsLiteral("connect_to_supplicant")) { + aResult.mStatus = mImpl->do_wifi_connect_to_supplicant(aInterface.get()); + } else if (aOptions.mCmd.EqualsLiteral("hostapd_command")) { + size_t len = BUFFER_SIZE - 1; + char buffer[BUFFER_SIZE]; + NS_ConvertUTF16toUTF8 request(aOptions.mRequest); + aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_command(request.get(), + buffer, + &len); + nsString value; + if (aResult.mStatus == 0) { + if (buffer[len - 1] == '\n') { // remove trailing new lines. + len--; + } + buffer[len] = '\0'; + CheckBuffer(buffer, len, value); + } + aResult.mReply = value; + } else if (aOptions.mCmd.EqualsLiteral("hostapd_get_stations")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_hostapd_get_stations(); + } else if (aOptions.mCmd.EqualsLiteral("connect_to_hostapd")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_connect_to_hostapd(); + } else if (aOptions.mCmd.EqualsLiteral("close_hostapd_connection")) { + aResult.mStatus = mWifiHotspotUtils->do_wifi_close_hostapd_connection(); + + } else { + NS_WARNING("WpaSupplicant::ExecuteCommand : Unknown command"); + printf_stderr("WpaSupplicant::ExecuteCommand : Unknown command: %s", + NS_ConvertUTF16toUTF8(aOptions.mCmd).get()); + return false; + } + + return true; +} + +// Checks the buffer and do the utf processing. +void +WpaSupplicant::CheckBuffer(char* buffer, int32_t length, + nsAString& aEvent) +{ + if (length <= 0 || length >= (BUFFER_SIZE - 1)) { + NS_WARNING("WpaSupplicant::CheckBuffer: Invalid buffer length"); + return; + } + + if (mSdkVersion < 18) { + buffer[length] = 0; + LossyConvertUTF8toUTF16(buffer, length, aEvent); + return; + } + + // After Android JB4.3, the SSIDs have been converted into printable form. + // In most of cases, SSIDs do not use unprintable characters, but IEEE 802.11 + // standard does not limit the used character set, so anything could be used + // in an SSID. Convert it to raw data form here. + char bytesBuffer[BUFFER_SIZE]; + uint32_t bytes = convertToBytes(bytesBuffer, length, buffer); + if (bytes <= 0 || bytes >= BUFFER_SIZE) { + NS_WARNING("WpaSupplicant::CheckBuffer: Invalid bytesbuffer length"); + return; + } + bytesBuffer[bytes] = 0; + LossyConvertUTF8toUTF16(bytesBuffer, bytes, aEvent); +} |