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
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)
}
}

View File

@@ -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 }

View File

@@ -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<ConfigHolder>()
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<P2pSupplicantConfiguration>(WifiP2pDialogFragment.KEY_CONFIGURER)
.update(data.getParcelableExtra(WifiP2pDialogFragment.KEY_CONFIGURATION))
val master = holder.config ?: return
val config = data.getParcelableExtra<WifiConfiguration>(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
}
}

View File

@@ -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<P2pSupplicantConfiguration> {
override fun createFromParcel(parcel: Parcel) = P2pSupplicantConfiguration(parcel.readString())
override fun newArray(size: Int): Array<P2pSupplicantConfiguration?> = 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<String>() {
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<String>) {
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<Any>()
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 {

View File

@@ -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<WifiConfiguration>(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() {