Add support for modifying repeater credentials

Credits go to @fxsheep: https://forum.xda-developers.com/showpost.php?p=76298728&postcount=5

Currently it only works on later versions of Android due to usage of `killall`. A workaround is in progress.
This commit is contained in:
Mygod
2018-04-21 20:18:15 -07:00
parent 6608a76297
commit 570998b255
15 changed files with 393 additions and 84 deletions

View File

@@ -1,12 +1,10 @@
package be.mygod.vpnhotspot
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.*
import android.databinding.BaseObservable
import android.databinding.Bindable
import android.databinding.DataBindingUtil
import android.net.wifi.WifiConfiguration
import android.net.wifi.p2p.WifiP2pDevice
import android.net.wifi.p2p.WifiP2pGroup
import android.os.Bundle
@@ -24,12 +22,15 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.*
import android.widget.EditText
import android.widget.Toast
import be.mygod.vpnhotspot.databinding.FragmentRepeaterBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding
import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
import be.mygod.vpnhotspot.net.wifi.WifiApDialog
import java.net.NetworkInterface
import java.net.SocketException
import java.util.*
@@ -60,7 +61,6 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
}
val ssid @Bindable get() = binder?.ssid ?: getText(R.string.repeater_inactive)
val password @Bindable get() = binder?.password ?: ""
val addresses @Bindable get(): String {
return try {
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
@@ -78,7 +78,6 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
}
fun onGroupChanged(group: WifiP2pGroup?) {
notifyPropertyChanged(BR.ssid)
notifyPropertyChanged(BR.password)
p2pInterface = group?.`interface`
notifyPropertyChanged(BR.addresses)
adapter.p2p = group?.clientList ?: emptyList()
@@ -227,19 +226,46 @@ class RepeaterFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickL
dialog.show()
true
} else false
R.id.resetGroup -> {
AlertDialog.Builder(requireContext())
.setTitle(R.string.repeater_reset_credentials)
.setMessage(getString(R.string.repeater_reset_credentials_dialog_message))
.setPositiveButton(R.string.repeater_reset_credentials_dialog_reset,
{ _, _ -> binder?.resetCredentials() })
.setNegativeButton(android.R.string.cancel, null)
.show()
R.id.edit -> {
editConfigurations()
true
}
else -> false
}
private fun editConfigurations() {
val binder = binder
val ssid = binder?.ssid
val context = requireContext()
if (ssid != null) {
val wifi = WifiConfiguration()
val conf = P2pSupplicantConfiguration()
wifi.SSID = ssid
wifi.preSharedKey = binder.password
if (wifi.preSharedKey == null || wifi.preSharedKey.length < 8) wifi.preSharedKey = conf.readPsk()
if (wifi.preSharedKey == null || wifi.preSharedKey.length < 8) {
Toast.makeText(context, R.string.root_unavailable, Toast.LENGTH_SHORT).show()
return
}
if (wifi.preSharedKey != null && wifi.preSharedKey.length >= 8) {
var dialog: WifiApDialog? = null
dialog = WifiApDialog(context, DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> when (conf.update(dialog!!.config!!)) {
true -> binder.requestGroupUpdate()
false -> Toast.makeText(context, R.string.noisy_su_failure, Toast.LENGTH_SHORT).show()
null -> Toast.makeText(context, R.string.root_unavailable, Toast.LENGTH_SHORT).show()
}
DialogInterface.BUTTON_NEUTRAL -> binder.resetCredentials()
}
}, wifi)
dialog.show()
return
}
}
Toast.makeText(context, R.string.repeater_configure_failure, Toast.LENGTH_LONG).show()
}
override fun onIpNeighbourAvailable(neighbours: Map<String, IpNeighbour>) {
adapter.neighbours = neighbours.values.toList()
}

View File

@@ -18,12 +18,12 @@ import android.widget.Toast
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.VpnMonitor
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.deletePersistentGroup
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.netId
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.requestPersistentGroupInfo
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.net.WifiP2pManagerHelper.startWps
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.netId
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
import java.net.InetAddress
import java.net.SocketException
@@ -74,6 +74,22 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
formatReason(R.string.repeater_reset_credentials_failure, reason), Toast.LENGTH_SHORT).show()
})
}
fun requestGroupUpdate() {
group = null
try {
p2pManager.requestPersistentGroupInfo(channel, {
when (it.size) {
0 -> { }
1 -> group = it.single()
else -> Log.w(TAG, "Unexpected groups: $it")
}
})
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this@RepeaterService, e.message, Toast.LENGTH_LONG).show()
}
}
}
private lateinit var p2pManager: WifiP2pManager
@@ -152,18 +168,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, VpnMonitor.Ca
override fun onChannelDisconnected() {
channel = p2pManager.initialize(this, Looper.getMainLooper(), this)
setOperatingChannel(true)
try {
p2pManager.requestPersistentGroupInfo(channel, {
when (it.size) {
0 -> { }
1 -> group = it.single()
else -> Log.w(TAG, "Unexpected groups: $it")
}
})
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
}
binder.requestGroupUpdate()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {

View File

@@ -30,7 +30,7 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
import be.mygod.vpnhotspot.databinding.ListitemManageTetherBinding
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.WifiApManager
import be.mygod.vpnhotspot.net.wifi.WifiApManager
import java.lang.reflect.InvocationTargetException
import java.net.NetworkInterface
import java.net.SocketException

View File

@@ -0,0 +1,61 @@
package be.mygod.vpnhotspot.net.wifi
import android.net.wifi.WifiConfiguration
import android.util.Log
import android.widget.Toast
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.loggerSu
import be.mygod.vpnhotspot.noisySu
import java.io.File
class P2pSupplicantConfiguration {
companion object {
private const val TAG = "P2pSupplicationConf"
// format for ssid is much more complicated, therefore we are only trying to find the line
private val ssidMatcher = "^[\\r\\t ]*ssid=".toRegex()
private val pskParser = "^[\\r\\t ]*psk=\"(.*)\"\$".toRegex(RegexOption.MULTILINE)
}
private val content by lazy { loggerSu("cat /data/misc/wifi/p2p_supplicant.conf") }
fun readPsk(): String? {
return try {
pskParser.findAll(content ?: return null).single().groupValues[1]
} catch (e: Exception) {
Log.w(TAG, content)
e.printStackTrace()
Toast.makeText(app, e.message, Toast.LENGTH_LONG).show()
null
}
}
fun update(config: WifiConfiguration): Boolean? {
val content = content ?: return null
val tempFile = File.createTempFile("vpnhotspot-", ".conf", app.cacheDir)
try {
var ssidFound = false
var pskFound = false
tempFile.printWriter().use {
for (line in content.lineSequence()) it.println(when {
ssidMatcher.containsMatchIn(line) -> {
ssidFound = true
"\tssid=" + config.SSID.toByteArray()
.joinToString("") { it.toInt().toString(16).padStart(2, '0') }
}
pskParser.containsMatchIn(line) -> {
pskFound = true
"\tpsk=\"${config.preSharedKey}\"" // no control chars or weird stuff
}
else -> line // do nothing
})
}
if (!ssidFound || !pskFound) {
Log.w(TAG, "Invalid conf ($ssidFound, $pskFound): $content")
return false
}
return noisySu("cat ${tempFile.absolutePath} > /data/misc/wifi/p2p_supplicant.conf", "killall wpa_supplicant")
} finally {
if (!tempFile.delete()) tempFile.deleteOnExit()
}
}
}

View File

@@ -0,0 +1,97 @@
package be.mygod.vpnhotspot.net.wifi
import android.content.Context
import android.content.DialogInterface
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiConfiguration.AuthAlgorithm
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.view.View
import android.widget.CheckBox
import android.widget.EditText
import android.widget.TextView
import be.mygod.vpnhotspot.R
import java.nio.charset.Charset
/**
* https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java
*/
class WifiApDialog(mContext: Context, private val mListener: DialogInterface.OnClickListener,
private val mWifiConfig: WifiConfiguration?) : AlertDialog(mContext), View.OnClickListener, TextWatcher {
companion object {
private const val BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE
}
private lateinit var mView: View
private lateinit var mSsid: TextView
private lateinit var mPassword: EditText
/**
* TODO: SSID in WifiConfiguration for soft ap
* is being stored as a raw string without quotes.
* This is not the case on the client side. We need to
* make things consistent and clean it up
*/
val config: WifiConfiguration?
get() {
val config = WifiConfiguration()
config.SSID = mSsid.text.toString()
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN)
if (mPassword.length() != 0) {
val password = mPassword.text.toString()
config.preSharedKey = password
}
return config
}
override fun onCreate(savedInstanceState: Bundle?) {
mView = layoutInflater.inflate(R.layout.dialog_wifi_ap, null)
setView(mView)
val context = context
setTitle(R.string.repeater_configure)
mSsid = mView.findViewById(R.id.ssid)
mPassword = mView.findViewById(R.id.password)
setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_save), mListener)
setButton(DialogInterface.BUTTON_NEGATIVE,
context.getString(R.string.wifi_cancel), mListener)
setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.repeater_reset_credentials), mListener)
if (mWifiConfig != null) {
mSsid.text = mWifiConfig.SSID
mPassword.setText(mWifiConfig.preSharedKey)
}
mSsid.addTextChangedListener(this)
mPassword.addTextChangedListener(this)
(mView.findViewById(R.id.show_password) as CheckBox).setOnClickListener(this)
super.onCreate(savedInstanceState)
validate()
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
mPassword.inputType = InputType.TYPE_CLASS_TEXT or
if ((mView.findViewById(R.id.show_password) as CheckBox).isChecked)
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
else InputType.TYPE_TEXT_VARIATION_PASSWORD
}
private fun validate() {
val mSsidString = mSsid.text.toString()
getButton(BUTTON_SUBMIT).isEnabled = mSsid.length() != 0 &&
mPassword.length() >= 8 && Charset.forName("UTF-8").encode(mSsidString).limit() <= 32
}
override fun onClick(view: View) {
mPassword.inputType = InputType.TYPE_CLASS_TEXT or if ((view as CheckBox).isChecked)
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
else
InputType.TYPE_TEXT_VARIATION_PASSWORD
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
override fun afterTextChanged(editable: Editable) {
validate()
}
}

View File

@@ -1,4 +1,4 @@
package be.mygod.vpnhotspot.net
package be.mygod.vpnhotspot.net.wifi
import android.content.Context
import android.net.wifi.WifiConfiguration

View File

@@ -1,4 +1,4 @@
package be.mygod.vpnhotspot.net
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
import android.net.wifi.WpsInfo