Fix parser forgetting all non-owned groups

This commit is contained in:
Mygod
2018-12-21 18:03:33 +08:00
parent 5281f14e0f
commit 690b923a43
5 changed files with 106 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot package be.mygod.vpnhotspot
import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@@ -16,4 +17,9 @@ abstract class AlertDialogFragment : DialogFragment(), DialogInterface.OnClickLi
override fun onClick(dialog: DialogInterface?, which: Int) { override fun onClick(dialog: DialogInterface?, which: Int) {
targetFragment!!.onActivityResult(targetRequestCode, which, data) targetFragment!!.onActivityResult(targetRequestCode, which, data)
} }
override fun onDismiss(dialog: DialogInterface?) {
super.onDismiss(dialog)
targetFragment!!.onActivityResult(targetRequestCode, Activity.RESULT_CANCELED, data)
}
} }

View File

@@ -63,6 +63,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
groupChanged(value) groupChanged(value)
} }
val groupChanged = StickyEvent1 { group } val groupChanged = StickyEvent1 { group }
var thisDevice: WifiP2pDevice? = null
fun startWps(pin: String? = null) { fun startWps(pin: String? = null) {
val channel = channel val channel = channel
@@ -113,17 +114,15 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP))
} }
} }
private var routingManager: LocalOnlyInterfaceManager? = null
private var thisDevice: WifiP2pDevice? = null
private val deviceListener = broadcastReceiver { _, intent -> private val deviceListener = broadcastReceiver { _, intent ->
when (intent.action) { when (intent.action) {
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_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() WifiP2pManagerHelper.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION -> onPersistentGroupsChanged()
} }
} }
private var routingManager: LocalOnlyInterfaceManager? = null
var status = Status.IDLE var status = Status.IDLE
private set(value) { private set(value) {
@@ -188,7 +187,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
private fun onPersistentGroupsChanged() { private fun onPersistentGroupsChanged() {
val channel = channel ?: return val channel = channel ?: return
val device = thisDevice ?: return val device = binder.thisDevice ?: return
try { try {
p2pManager.requestPersistentGroupInfo(channel) { p2pManager.requestPersistentGroupInfo(channel) {
val ownedGroups = it.filter { it.isGroupOwner && it.owner.deviceAddress == device.deviceAddress } val ownedGroups = it.filter { it.isGroupOwner && it.owner.deviceAddress == device.deviceAddress }

View File

@@ -15,6 +15,9 @@ import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.databinding.BaseObservable import androidx.databinding.BaseObservable
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.get
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.* import be.mygod.vpnhotspot.*
import be.mygod.vpnhotspot.App.Companion.app 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.util.formatAddresses
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
import java.lang.RuntimeException
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.SocketException import java.net.SocketException
@@ -89,23 +93,20 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
} }
fun editConfigurations() { fun editConfigurations() {
val binder = binder
val group = binder?.group val group = binder?.group
val ssid = group?.networkName if (group != null) try {
if (ssid != null) { val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress)
val wifi = WifiConfiguration() holder.config = config
val conf = P2pSupplicantConfiguration() WifiP2pDialogFragment().apply {
wifi.SSID = ssid arguments = bundleOf(Pair(WifiP2pDialogFragment.KEY_CONFIGURATION, WifiConfiguration().apply {
wifi.preSharedKey = group.passphrase SSID = config.ssid
if (wifi.preSharedKey == null) wifi.preSharedKey = conf.readPsk() preSharedKey = config.psk
if (wifi.preSharedKey != null) { }))
WifiP2pDialogFragment().apply { setTargetFragment(parent, TetheringFragment.REPEATER_EDIT_CONFIGURATION)
arguments = bundleOf(Pair(WifiP2pDialogFragment.KEY_CONFIGURATION, wifi), }.show(parent.fragmentManager, WifiP2pDialogFragment.TAG)
Pair(WifiP2pDialogFragment.KEY_CONFIGURER, conf)) return
setTargetFragment(parent, TetheringFragment.REPEATER_EDIT_CONFIGURATION) } catch (e: RuntimeException) {
}.show(parent.fragmentManager, WifiP2pDialogFragment.TAG) Timber.w(e)
return
}
} }
SmartSnackbar.make(R.string.repeater_configure_failure).show() 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 { init {
ServiceForegroundConnector(parent, this, RepeaterService::class) ServiceForegroundConnector(parent, this, RepeaterService::class)
} }
@@ -139,6 +144,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
private val data = Data() private val data = Data()
internal var binder: RepeaterService.Binder? = null internal var binder: RepeaterService.Binder? = null
private var p2pInterface: String? = null private var p2pInterface: String? = null
private val holder = ViewModelProviders.of(parent).get<ConfigHolder>()
override fun bindTo(viewHolder: RecyclerView.ViewHolder) { override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
(viewHolder as ViewHolder).binding.data = data (viewHolder as ViewHolder).binding.data = data
@@ -169,8 +175,9 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
fun onEditResult(which: Int, data: Intent) { fun onEditResult(which: Int, data: Intent) {
when (which) { when (which) {
DialogInterface.BUTTON_POSITIVE -> try { DialogInterface.BUTTON_POSITIVE -> try {
data.getParcelableExtra<P2pSupplicantConfiguration>(WifiP2pDialogFragment.KEY_CONFIGURER) val master = holder.config ?: return
.update(data.getParcelableExtra(WifiP2pDialogFragment.KEY_CONFIGURATION)) val config = data.getParcelableExtra<WifiConfiguration>(WifiP2pDialogFragment.KEY_CONFIGURATION)
master.update(config.SSID, config.preSharedKey)
binder!!.group = null binder!!.group = null
} catch (e: Exception) { } catch (e: Exception) {
Timber.w(e) Timber.w(e)
@@ -178,5 +185,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
} }
DialogInterface.BUTTON_NEUTRAL -> binder!!.resetCredentials() DialogInterface.BUTTON_NEUTRAL -> binder!!.resetCredentials()
} }
holder.config = null
} }
} }

View File

@@ -1,87 +1,94 @@
package be.mygod.vpnhotspot.net.wifi package be.mygod.vpnhotspot.net.wifi
import android.net.wifi.WifiConfiguration import android.net.wifi.p2p.WifiP2pGroup
import android.os.Build import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.util.RootSession import be.mygod.vpnhotspot.util.RootSession
import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException
class P2pSupplicantConfiguration(private val initContent: String? = null) : Parcelable { /**
companion object CREATOR : Parcelable.Creator<P2pSupplicantConfiguration> { * This parser is based on:
override fun createFromParcel(parcel: Parcel) = P2pSupplicantConfiguration(parcel.readString()) * https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488
override fun newArray(size: Int): Array<P2pSupplicantConfiguration?> = arrayOfNulls(size) * https://android.googlesource.com/platform/external/wpa_supplicant_8/+/6fa46df/wpa_supplicant/config_file.c#182
*/
/** class P2pSupplicantConfiguration(private val group: WifiP2pGroup, ownerAddress: String?) {
* Format for ssid is much more complicated, therefore we are only trying to find the line and rely on companion object {
* Android's results instead. private val networkParser =
* "^(bssid=(([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})|psk=(ext:|\"(.*)\"|[0-9a-fA-F]{64}\$))".toRegex()
* 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)
private val whitespaceMatcher = "\\s+".toRegex() private val whitespaceMatcher = "\\s+".toRegex()
private val confPath = if (Build.VERSION.SDK_INT >= 28) private val confPath = if (Build.VERSION.SDK_INT >= 28)
"/data/vendor/wifi/wpa/p2p_supplicant.conf" else "/data/misc/wifi/p2p_supplicant.conf" "/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) { private class NetworkBlock : ArrayList<String>() {
out.writeString(if (contentDelegate.isInitialized()) content else null) 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 class Parser(lines: List<String>) {
private val content by contentDelegate 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? { private val content by lazy {
return try { RootSession.use {
val match = pskParser.findAll(content).single() val result = ArrayList<Any>()
if (match.groups[2] == null && match.groups[3] == null) "" else { var target: NetworkBlock? = null
// only one will match and hold non-empty value val parser = Parser(it.execOutUnjoined("cat $confPath"))
val result = match.groupValues[2] + match.groupValues[3] while (parser.next()) {
check(result.length in 8..63) if (parser.trimmed.startsWith("network={")) {
result 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) { Pair(result, target!!)
null
} catch (e: RuntimeException) {
if (contentDelegate.isInitialized()) Timber.w(content)
Timber.w(e)
null
} }
} }
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) val tempFile = File.createTempFile("vpnhotspot-", ".conf", app.cacheDir)
try { try {
var ssidFound = 0 tempFile.printWriter().use { writer ->
var pskFound = 0 lines.forEach { writer.println(it) }
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())
} }
// pkill not available on Lollipop. Source: https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/README.md // pkill not available on Lollipop. Source: https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/README.md
RootSession.use { RootSession.use {

View File

@@ -25,13 +25,11 @@ class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher, DialogInterfac
companion object { companion object {
const val TAG = "WifiP2pDialogFragment" const val TAG = "WifiP2pDialogFragment"
const val KEY_CONFIGURATION = "configuration" const val KEY_CONFIGURATION = "configuration"
const val KEY_CONFIGURER = "configurer"
} }
private lateinit var mView: View private lateinit var mView: View
private lateinit var mSsid: TextView private lateinit var mSsid: TextView
private lateinit var mPassword: EditText private lateinit var mPassword: EditText
private lateinit var configurer: P2pSupplicantConfiguration
private val config: WifiConfiguration? private val config: WifiConfiguration?
get() { get() {
val config = WifiConfiguration() val config = WifiConfiguration()
@@ -54,7 +52,6 @@ class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher, DialogInterfac
setNegativeButton(context.getString(R.string.wifi_cancel), null) setNegativeButton(context.getString(R.string.wifi_cancel), null)
setNeutralButton(context.getString(R.string.repeater_reset_credentials), listener) setNeutralButton(context.getString(R.string.repeater_reset_credentials), listener)
val arguments = arguments!! val arguments = arguments!!
configurer = arguments.getParcelable(KEY_CONFIGURER)!!
val mWifiConfig = arguments.getParcelable<WifiConfiguration>(KEY_CONFIGURATION) val mWifiConfig = arguments.getParcelable<WifiConfiguration>(KEY_CONFIGURATION)
if (mWifiConfig != null) { if (mWifiConfig != null) {
mSsid.text = mWifiConfig.SSID mSsid.text = mWifiConfig.SSID
@@ -66,7 +63,6 @@ class WifiP2pDialogFragment : AlertDialogFragment(), TextWatcher, DialogInterfac
override val data get() = Intent().apply { override val data get() = Intent().apply {
putExtra(KEY_CONFIGURATION, config) putExtra(KEY_CONFIGURATION, config)
putExtra(KEY_CONFIGURER, configurer)
} }
override fun onStart() { override fun onStart() {