Fix parser forgetting all non-owned groups
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user