From 690b923a43468f3f96d778975976c9af014d85ab Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 21 Dec 2018 18:03:33 +0800 Subject: [PATCH] Fix parser forgetting all non-owned groups --- .../mygod/vpnhotspot/AlertDialogFragment.kt | 6 + .../be/mygod/vpnhotspot/RepeaterService.kt | 9 +- .../vpnhotspot/manage/RepeaterManager.kt | 44 +++--- .../net/wifi/P2pSupplicantConfiguration.kt | 133 +++++++++--------- .../net/wifi/WifiP2pDialogFragment.kt | 4 - 5 files changed, 106 insertions(+), 90 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt index 9d44cb06..7b8eb03f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt @@ -1,5 +1,6 @@ package be.mygod.vpnhotspot +import android.app.Activity import android.content.DialogInterface import android.content.Intent import android.os.Bundle @@ -16,4 +17,9 @@ abstract class AlertDialogFragment : DialogFragment(), DialogInterface.OnClickLi override fun onClick(dialog: DialogInterface?, which: Int) { targetFragment!!.onActivityResult(targetRequestCode, which, data) } + + override fun onDismiss(dialog: DialogInterface?) { + super.onDismiss(dialog) + targetFragment!!.onActivityResult(targetRequestCode, Activity.RESULT_CANCELED, data) + } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 77e09b28..ecac92b6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -63,6 +63,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere groupChanged(value) } val groupChanged = StickyEvent1 { group } + var thisDevice: WifiP2pDevice? = null fun startWps(pin: String? = null) { val channel = channel @@ -113,17 +114,15 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)) } } - private var routingManager: LocalOnlyInterfaceManager? = null - - private var thisDevice: WifiP2pDevice? = null private val deviceListener = broadcastReceiver { _, intent -> when (intent.action) { WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { - thisDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) + binder.thisDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) } WifiP2pManagerHelper.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION -> onPersistentGroupsChanged() } } + private var routingManager: LocalOnlyInterfaceManager? = null var status = Status.IDLE private set(value) { @@ -188,7 +187,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere private fun onPersistentGroupsChanged() { val channel = channel ?: return - val device = thisDevice ?: return + val device = binder.thisDevice ?: return try { p2pManager.requestPersistentGroupInfo(channel) { val ownedGroups = it.filter { it.isGroupOwner && it.owner.deviceAddress == device.deviceAddress } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index ab1451d2..7b8bc322 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -15,6 +15,9 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.databinding.BaseObservable import androidx.databinding.Bindable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.get import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.* import be.mygod.vpnhotspot.App.Companion.app @@ -25,6 +28,7 @@ import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.formatAddresses import be.mygod.vpnhotspot.widget.SmartSnackbar import timber.log.Timber +import java.lang.RuntimeException import java.net.NetworkInterface import java.net.SocketException @@ -89,23 +93,20 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } fun editConfigurations() { - val binder = binder val group = binder?.group - val ssid = group?.networkName - if (ssid != null) { - val wifi = WifiConfiguration() - val conf = P2pSupplicantConfiguration() - wifi.SSID = ssid - wifi.preSharedKey = group.passphrase - if (wifi.preSharedKey == null) wifi.preSharedKey = conf.readPsk() - if (wifi.preSharedKey != null) { - WifiP2pDialogFragment().apply { - arguments = bundleOf(Pair(WifiP2pDialogFragment.KEY_CONFIGURATION, wifi), - Pair(WifiP2pDialogFragment.KEY_CONFIGURER, conf)) - setTargetFragment(parent, TetheringFragment.REPEATER_EDIT_CONFIGURATION) - }.show(parent.fragmentManager, WifiP2pDialogFragment.TAG) - return - } + if (group != null) try { + val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress) + holder.config = config + WifiP2pDialogFragment().apply { + arguments = bundleOf(Pair(WifiP2pDialogFragment.KEY_CONFIGURATION, WifiConfiguration().apply { + SSID = config.ssid + preSharedKey = config.psk + })) + setTargetFragment(parent, TetheringFragment.REPEATER_EDIT_CONFIGURATION) + }.show(parent.fragmentManager, WifiP2pDialogFragment.TAG) + return + } catch (e: RuntimeException) { + Timber.w(e) } SmartSnackbar.make(R.string.repeater_configure_failure).show() } @@ -131,6 +132,10 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } } + class ConfigHolder : ViewModel() { + var config: P2pSupplicantConfiguration? = null + } + init { ServiceForegroundConnector(parent, this, RepeaterService::class) } @@ -139,6 +144,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic private val data = Data() internal var binder: RepeaterService.Binder? = null private var p2pInterface: String? = null + private val holder = ViewModelProviders.of(parent).get() override fun bindTo(viewHolder: RecyclerView.ViewHolder) { (viewHolder as ViewHolder).binding.data = data @@ -169,8 +175,9 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic fun onEditResult(which: Int, data: Intent) { when (which) { DialogInterface.BUTTON_POSITIVE -> try { - data.getParcelableExtra(WifiP2pDialogFragment.KEY_CONFIGURER) - .update(data.getParcelableExtra(WifiP2pDialogFragment.KEY_CONFIGURATION)) + val master = holder.config ?: return + val config = data.getParcelableExtra(WifiP2pDialogFragment.KEY_CONFIGURATION) + master.update(config.SSID, config.preSharedKey) binder!!.group = null } catch (e: Exception) { Timber.w(e) @@ -178,5 +185,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } DialogInterface.BUTTON_NEUTRAL -> binder!!.resetCredentials() } + holder.config = null } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt index 1116e106..e2ecd73d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt @@ -1,87 +1,94 @@ package be.mygod.vpnhotspot.net.wifi -import android.net.wifi.WifiConfiguration +import android.net.wifi.p2p.WifiP2pGroup import android.os.Build -import android.os.Parcel -import android.os.Parcelable import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.util.RootSession -import timber.log.Timber import java.io.File -import java.io.IOException -class P2pSupplicantConfiguration(private val initContent: String? = null) : Parcelable { - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel) = P2pSupplicantConfiguration(parcel.readString()) - override fun newArray(size: Int): Array = arrayOfNulls(size) - - /** - * Format for ssid is much more complicated, therefore we are only trying to find the line and rely on - * Android's results instead. - * - * Source: https://android.googlesource.com/platform/external/wpa_supplicant_8/+/2933359/src/utils/common.c#631 - */ - private val ssidMatcher = "^[\\r\\t ]*ssid=".toRegex() - /** - * PSK parser can be found here: https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488 - */ - private val pskParser = "^[\\r\\t ]*psk=(ext:|\"(.*)\"|\"(.*)|[0-9a-fA-F]{64}\$)".toRegex(RegexOption.MULTILINE) +/** + * This parser is based on: + * https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488 + * https://android.googlesource.com/platform/external/wpa_supplicant_8/+/6fa46df/wpa_supplicant/config_file.c#182 + */ +class P2pSupplicantConfiguration(private val group: WifiP2pGroup, ownerAddress: String?) { + companion object { + private val networkParser = + "^(bssid=(([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})|psk=(ext:|\"(.*)\"|[0-9a-fA-F]{64}\$))".toRegex() private val whitespaceMatcher = "\\s+".toRegex() - private val confPath = if (Build.VERSION.SDK_INT >= 28) "/data/vendor/wifi/wpa/p2p_supplicant.conf" else "/data/misc/wifi/p2p_supplicant.conf" } - private class InvalidConfigurationError : IOException("Invalid configuration") - override fun writeToParcel(out: Parcel, flags: Int) { - out.writeString(if (contentDelegate.isInitialized()) content else null) + private class NetworkBlock : ArrayList() { + var ssidLine: Int? = null + var pskLine: Int? = null + var psk: String? = null + var groupOwner = false + var bssidMatches = false + + override fun toString() = joinToString("\n") } - override fun describeContents() = 0 - private val contentDelegate = lazy { initContent ?: RootSession.use { it.execOut("cat $confPath") } } - private val content by contentDelegate + private class Parser(lines: List) { + private val iterator = lines.iterator() + lateinit var line: String + lateinit var trimmed: String + fun next() = if (iterator.hasNext()) { + line = iterator.next().apply { trimmed = trimStart('\r', '\t', ' ') } + true + } else false + } - fun readPsk(): String? { - return try { - val match = pskParser.findAll(content).single() - if (match.groups[2] == null && match.groups[3] == null) "" else { - // only one will match and hold non-empty value - val result = match.groupValues[2] + match.groupValues[3] - check(result.length in 8..63) - result + private val content by lazy { + RootSession.use { + val result = ArrayList() + var target: NetworkBlock? = null + val parser = Parser(it.execOutUnjoined("cat $confPath")) + while (parser.next()) { + if (parser.trimmed.startsWith("network={")) { + val block = NetworkBlock() + block.add(parser.line) + while (parser.next() && !parser.trimmed.startsWith('}')) { + if (parser.trimmed.startsWith("ssid=")) { + check(block.ssidLine == null) + block.ssidLine = block.size + } else if (parser.trimmed.startsWith("mode=3")) block.groupOwner = true else { + val match = networkParser.find(parser.trimmed) + if (match != null) if (match.groups[5] != null) { + check(block.pskLine == null && block.psk == null) + block.psk = match.groupValues[5].apply { check(length in 8..63) } + block.pskLine = block.size + } else if (match.groups[2] != null && + match.groupValues[2].equals(group.owner.deviceAddress ?: ownerAddress, true)) { + block.bssidMatches = true + } + } + block.add(parser.line) + } + block.add(parser.line) + result.add(block) + if (block.bssidMatches && block.groupOwner && target == null) { // keep first only + check(block.ssidLine != null && block.pskLine != null) + target = block + } + } else result.add(parser.line) } - } catch (e: NoSuchElementException) { - null - } catch (e: RuntimeException) { - if (contentDelegate.isInitialized()) Timber.w(content) - Timber.w(e) - null + Pair(result, target!!) } } + val ssid = group.networkName + val psk = group.passphrase ?: content.second.psk!! - fun update(config: WifiConfiguration) { + fun update(ssid: String, psk: String) { + val (lines, block) = content + block[block.ssidLine!!] = "\tssid=" + ssid.toByteArray() + .joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') } + block[block.pskLine!!] = "\tpsk=\"$psk\"" // no control chars or weird stuff val tempFile = File.createTempFile("vpnhotspot-", ".conf", app.cacheDir) try { - var ssidFound = 0 - var pskFound = 0 - tempFile.printWriter().use { - for (line in content.lineSequence()) it.println(when { - ssidMatcher.containsMatchIn(line) -> { - ssidFound += 1 - "\tssid=" + config.SSID.toByteArray() - .joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') } - } - pskParser.containsMatchIn(line) -> { - pskFound += 1 - "\tpsk=\"${config.preSharedKey}\"" // no control chars or weird stuff - } - else -> line // do nothing - }) - } - if (ssidFound != 1 || pskFound != 1) { - Timber.w("Invalid conf ($ssidFound, $pskFound): $content") - if (ssidFound == 0 || pskFound == 0) throw InvalidConfigurationError() - else Timber.i(InvalidConfigurationError()) + tempFile.printWriter().use { writer -> + lines.forEach { writer.println(it) } } // pkill not available on Lollipop. Source: https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/README.md RootSession.use { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt index bfe6df7d..7412b024 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pDialogFragment.kt @@ -25,13 +25,11 @@ class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher, DialogInterfac companion object { const val TAG = "WifiP2pDialogFragment" const val KEY_CONFIGURATION = "configuration" - const val KEY_CONFIGURER = "configurer" } private lateinit var mView: View private lateinit var mSsid: TextView private lateinit var mPassword: EditText - private lateinit var configurer: P2pSupplicantConfiguration private val config: WifiConfiguration? get() { val config = WifiConfiguration() @@ -54,7 +52,6 @@ class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher, DialogInterfac setNegativeButton(context.getString(R.string.wifi_cancel), null) setNeutralButton(context.getString(R.string.repeater_reset_credentials), listener) val arguments = arguments!! - configurer = arguments.getParcelable(KEY_CONFIGURER)!! val mWifiConfig = arguments.getParcelable(KEY_CONFIGURATION) if (mWifiConfig != null) { mSsid.text = mWifiConfig.SSID @@ -66,7 +63,6 @@ class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher, DialogInterfac override val data get() = Intent().apply { putExtra(KEY_CONFIGURATION, config) - putExtra(KEY_CONFIGURER, configurer) } override fun onStart() {