diff --git a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
index 4882a3fe58..80d5cb2ee8 100644
--- a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
+++ b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
@@ -64,6 +64,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
}
private var configFile: File? = null
+ private var socks5AuthFile: File? = null
var trafficMonitor: TrafficMonitor? = null
val plugin by lazy { PluginManager.init(PluginConfiguration(profile.plugin ?: "")) }
@@ -86,6 +87,11 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
}
config.put("dns", "unix://local_dns_path")
config.put("mode", mode)
+ val socksPassword = DataStore.socksPassword
+ // compute auth file path before building JSON so the absolute path can be embedded
+ val authFile = if (socksPassword.isNotEmpty()) {
+ File(configFile.parentFile, "${configFile.name}_auth")
+ } else null
config.put("locals", JSONArray().apply {
// local SOCKS5 proxy
put(JSONObject().apply {
@@ -94,6 +100,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
put("local_udp_address", DataStore.listenAddress)
put("local_udp_port", DataStore.portProxy)
put("mode", mode)
+ if (authFile != null) put("socks5_auth_config_path", authFile.absolutePath)
})
// local DNS proxy
@@ -114,6 +121,21 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
})
configFile.writeText(config.toString())
+ // write SOCKS5 auth config file if password protection is enabled
+ if (authFile != null) {
+ socks5AuthFile = authFile
+ authFile.writeText(JSONObject().apply {
+ put("password", JSONObject().apply {
+ put("users", JSONArray().apply {
+ put(JSONObject().apply {
+ put("user_name", "shadowsocks")
+ put("password", socksPassword)
+ })
+ })
+ })
+ }.toString())
+ }
+
// build the command line
val cmd = arrayListOf(
File((service as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath,
@@ -143,5 +165,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
trafficMonitor = null
configFile?.delete() // remove old config possibly in device storage
configFile = null
+ socks5AuthFile?.delete()
+ socks5AuthFile = null
}
}
diff --git a/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt b/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
index 5716d1fc09..17c58d7020 100644
--- a/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
+++ b/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
@@ -214,6 +214,13 @@ class VpnService : BaseVpnService(), BaseService.Interface {
"--sock-path", "sock_path",
"--dnsgw", "127.0.0.1:${DataStore.portLocalDns}",
"--loglevel", "warning")
+ val socksPassword = DataStore.socksPassword
+ if (socksPassword.isNotEmpty()) {
+ cmd += "--username"
+ cmd += "shadowsocks"
+ cmd += "--password"
+ cmd += socksPassword
+ }
if (profile.ipv6) {
cmd += "--netif-ip6addr"
cmd += PRIVATE_VLAN6_ROUTER
diff --git a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt
index 5f3a772bf4..44dac24343 100644
--- a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt
+++ b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt
@@ -76,6 +76,9 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var portTransproxy: Int
get() = getLocalPort(Key.portTransproxy, 8200)
set(value) = publicStore.putString(Key.portTransproxy, value.toString())
+ var socksPassword: String
+ get() = publicStore.getString(Key.socksPassword) ?: ""
+ set(value) = publicStore.putString(Key.socksPassword, value)
/**
* Initialize settings that have complicated default values.
diff --git a/core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt b/core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt
index 1385c77ed5..8c09f5fbed 100644
--- a/core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt
+++ b/core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt
@@ -22,6 +22,7 @@ package com.github.shadowsocks.preference
import android.graphics.Typeface
import android.text.InputFilter
+import android.text.InputType
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.preference.EditTextPreference
@@ -33,6 +34,13 @@ object EditTextPreferenceModifiers {
}
}
+ object Password : EditTextPreference.OnBindEditTextListener {
+ override fun onBindEditText(editText: EditText) {
+ editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ editText.setSingleLine()
+ }
+ }
+
object Port : EditTextPreference.OnBindEditTextListener {
private val portLengthFilter = arrayOf(InputFilter.LengthFilter(5))
diff --git a/core/src/main/java/com/github/shadowsocks/utils/Constants.kt b/core/src/main/java/com/github/shadowsocks/utils/Constants.kt
index 52b6de20ec..0644672ad3 100644
--- a/core/src/main/java/com/github/shadowsocks/utils/Constants.kt
+++ b/core/src/main/java/com/github/shadowsocks/utils/Constants.kt
@@ -40,6 +40,7 @@ object Key {
const val portProxy = "portProxy"
const val portLocalDns = "portLocalDns"
const val portTransproxy = "portTransproxy"
+ const val socksPassword = "socksPassword"
const val route = "route"
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 04ed7d9d67..87071c002e 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -14,6 +14,8 @@
SOCKS5 proxy port
Local DNS port
Transproxy port
+ SOCKS5 proxy password
+ Set a password to protect the local SOCKS5 proxy from unauthorized use
Remote DNS
%1$s↑\t%2$s↓
diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt
index cc7ac91647..2b503a6696 100644
--- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt
+++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt
@@ -59,6 +59,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val portTransproxy = findPreference(Key.portTransproxy)!!
portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
+ val socksPassword = findPreference(Key.socksPassword)!!
val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
portTransproxy.isEnabled = newValue as String? == Key.modeTransproxy
true
@@ -68,6 +69,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
serviceMode.isEnabled = stopped
portProxy.isEnabled = stopped
portLocalDns.isEnabled = stopped
+ socksPassword.isEnabled = stopped
if (stopped) onServiceModeChange.onPreferenceChange(serviceMode, DataStore.serviceMode) else {
portTransproxy.isEnabled = false
}
diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml
index 9a4808d8b3..29b3369332 100644
--- a/mobile/src/main/res/xml/pref_global.xml
+++ b/mobile/src/main/res/xml/pref_global.xml
@@ -34,4 +34,10 @@
app:key="portTransproxy"
app:title="@string/port_transproxy"
app:useSimpleSummaryProvider="true"/>
+
diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
index 198245f728..182eecead0 100644
--- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
+++ b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
@@ -56,6 +56,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private lateinit var portProxy: EditTextPreference
private lateinit var portLocalDns: EditTextPreference
private lateinit var portTransproxy: EditTextPreference
+ private lateinit var socksPassword: EditTextPreference
private val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
portTransproxy.isEnabled = newValue as String? == Key.modeTransproxy
true
@@ -101,6 +102,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
shareOverLan.isEnabled = stopped
portProxy.isEnabled = stopped
portLocalDns.isEnabled = stopped
+ socksPassword.isEnabled = stopped
if (stopped) onServiceModeChange.onPreferenceChange(serviceMode, DataStore.serviceMode) else {
portTransproxy.isEnabled = false
}
@@ -141,6 +143,8 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
portTransproxy = findPreference(Key.portTransproxy)!!
portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
+ socksPassword = findPreference(Key.socksPassword)!!
+ socksPassword.setOnBindEditTextListener(EditTextPreferenceModifiers.Password)
serviceMode.onPreferenceChangeListener = onServiceModeChange
findPreference(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME)
diff --git a/tv/src/main/res/xml/pref_main.xml b/tv/src/main/res/xml/pref_main.xml
index 1e60152712..67e8dc963d 100644
--- a/tv/src/main/res/xml/pref_main.xml
+++ b/tv/src/main/res/xml/pref_main.xml
@@ -48,6 +48,11 @@
app:key="portTransproxy"
app:title="@string/port_transproxy"
app:useSimpleSummaryProvider="true"/>
+