Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/java/io/nekohasekai/sagernet/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ object Key {
const val BYPASS_LAN_IN_CORE = "bypassLanInCore"

const val MIXED_PORT = "mixedPort"
const val MIXED_USERNAME = "mixedUsername"
const val MIXED_PASSWORD = "mixedPassword"
const val ALLOW_ACCESS = "allowAccess"
const val DISABLE_MIXED = "disableMixed"
const val SPEED_INTERVAL = "speedInterval"
const val SHOW_DIRECT_SPEED = "showDirectSpeed"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ abstract class BoxInstance(
buildConfig()
for ((chain) in config.externalIndex) {
chain.entries.forEachIndexed { index, (port, profile) ->
val creds = config.localProxyCredentials[port] ?: error("No local proxy credentials for port $port")
val (localProxyUsername, localProxyPassword) = creds
when (val bean = profile.requireBean()) {
is TrojanGoBean -> {
initPlugin("trojan-go-plugin")
Expand All @@ -69,7 +71,7 @@ abstract class BoxInstance(

is NaiveBean -> {
initPlugin("naive-plugin")
pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port)
pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port, localProxyUsername, localProxyPassword)
}

is HysteriaBean -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.nekohasekai.sagernet.ktx.string
import io.nekohasekai.sagernet.ktx.stringToInt
import io.nekohasekai.sagernet.ktx.stringToIntIfExists
import moe.matsuri.nb4a.TempDatabase
import moe.matsuri.nb4a.utils.Util

object DataStore : OnPreferenceDataStoreChangeListener {

Expand Down Expand Up @@ -128,13 +129,20 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var mixedPort: Int
get() = getLocalPort(Key.MIXED_PORT, 2080)
set(value) = saveLocalPort(Key.MIXED_PORT, value)
var mixedUsername by configurationStore.string(Key.MIXED_USERNAME) { "User" }
var mixedPassword by configurationStore.string(Key.MIXED_PASSWORD) { Util.generateCryptoSecurePassword() }

fun initGlobal() {
if (configurationStore.getString(Key.MIXED_PORT) == null) {
mixedPort = mixedPort
}

if (configurationStore.getString(Key.MIXED_PASSWORD) == null) {
mixedPassword = Util.generateCryptoSecurePassword()
}
}

var disableMixed by configurationStore.boolean(Key.DISABLE_MIXED)

private fun getLocalPort(key: String, default: Int): Int {
return parsePort(configurationStore.getString(key), default + userIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,11 @@ data class ProxyEntity(
}

is NaiveBean -> {
val localProxyCredentials = config.localProxyCredentials[port]
val localProxyUsername = localProxyCredentials?.first ?: ""
val localProxyPassword = localProxyCredentials?.second ?: ""
append("\n\n")
append(bean.buildNaiveConfig(port))
append(bean.buildNaiveConfig(port, localProxyUsername, localProxyPassword))
}

is HysteriaBean -> {
Expand Down
41 changes: 29 additions & 12 deletions app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ class ConfigBuildResult(
var trafficMap: Map<String, List<ProxyEntity>>,
var profileTagMap: Map<Long, String>,
val selectorGroupId: Long,
val localProxyCredentials: Map<Int, Pair<String, String>>,
) {
data class IndexEntity(var chain: LinkedHashMap<Int, ProxyEntity>)
}

fun buildConfig(
proxy: ProxyEntity, forTest: Boolean = false, forExport: Boolean = false
): ConfigBuildResult {

val localProxyCredentials = HashMap<Int, Pair<String, String>>()

if (proxy.type == TYPE_CONFIG) {
val bean = proxy.requireBean() as ConfigBean
if (bean.type == 0) {
Expand All @@ -72,7 +74,8 @@ fun buildConfig(
proxy.id, //
mapOf(TAG_PROXY to listOf(proxy)), //
mapOf(proxy.id to TAG_PROXY), //
-1L
-1L,
localProxyCredentials
)
}
}
Expand Down Expand Up @@ -224,15 +227,23 @@ fun buildConfig(
}
}
})
inbounds.add(Inbound_MixedOptions().apply {
type = "mixed"
tag = TAG_MIXED
listen = bind
listen_port = DataStore.mixedPort
domain_strategy = genDomainStrategy(DataStore.resolveDestination)
sniff = needSniff
sniff_override_destination = needSniffOverride
})
if (!DataStore.disableMixed) {
inbounds.add(Inbound_MixedOptions().apply {
type = "mixed"
tag = TAG_MIXED
listen = bind
listen_port = DataStore.mixedPort
domain_strategy = genDomainStrategy(DataStore.resolveDestination)
sniff = needSniff
sniff_override_destination = needSniffOverride
users = listOf(
User().apply {
username = DataStore.mixedUsername
password = DataStore.mixedPassword
},
)
})
}
}

outbounds = mutableListOf()
Expand Down Expand Up @@ -326,11 +337,16 @@ fun buildConfig(

if (proxyEntity.needExternal()) { // externel outbound
val localPort = mkPort()
val localProxyUsername = Util.generateCryptoSecurePassword()
val localProxyPassword = Util.generateCryptoSecurePassword()
externalChainMap[localPort] = proxyEntity
localProxyCredentials[localPort] = localProxyUsername to localProxyPassword
currentOutbound = Outbound_SocksOptions().apply {
type = "socks"
server = LOCALHOST
server_port = localPort
username = localProxyUsername
password = localProxyPassword
}
} else {
// internal outbound
Expand Down Expand Up @@ -748,7 +764,8 @@ fun buildConfig(
proxy.id,
trafficMap,
tagMap,
if (buildSelector) group.id else -1L
if (buildSelector) group.id else -1L,
localProxyCredentials
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.nekohasekai.sagernet.fmt.LOCALHOST
import io.nekohasekai.sagernet.ktx.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.json.JSONObject
import android.net.Uri

fun parseNaive(link: String): NaiveBean {
val proto = link.substringAfter("+").substringBefore(":")
Expand Down Expand Up @@ -54,7 +55,7 @@ fun NaiveBean.toUri(proxyOnly: Boolean = false): String {
return builder.toLink(if (proxyOnly) proto else "naive+$proto", false)
}

fun NaiveBean.buildNaiveConfig(port: Int): String {
fun NaiveBean.buildNaiveConfig(port: Int, localProxyUsername: String, localProxyPassword: String): String {
return JSONObject().apply {
// process ipv6
finalAddress = finalAddress.wrapIPV6Host()
Expand All @@ -75,7 +76,10 @@ fun NaiveBean.buildNaiveConfig(port: Int): String {
}
}

put("listen", "socks://$LOCALHOST:$port")
val usernameEnc = Uri.encode(localProxyUsername)
val passwordEnc = Uri.encode(localProxyPassword)

put("listen", "socks://$usernameEnc:$passwordEnc@$LOCALHOST:$port")
put("proxy", toUri(true))
if (extraHeaders.isNotBlank()) {
put("extra-headers", extraHeaders.split("\n").joinToString("\r\n"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
true
}
val mixedPort = findPreference<EditTextPreference>(Key.MIXED_PORT)!!
val mixedUsername = findPreference<EditTextPreference>(Key.MIXED_USERNAME)!!
val mixedPassword = findPreference<EditTextPreference>(Key.MIXED_PASSWORD)!!
val disableMixed = findPreference<SwitchPreference>(Key.DISABLE_MIXED)!!
val serviceMode = findPreference<Preference>(Key.SERVICE_MODE)!!
val allowAccess = findPreference<Preference>(Key.ALLOW_ACCESS)!!
val appendHttpProxy = findPreference<SwitchPreference>(Key.APPEND_HTTP_PROXY)!!
Expand Down Expand Up @@ -148,7 +151,30 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
true
}

val mixedPrefsToToggle = listOf(
findPreference<Preference>("mixedPort"),
findPreference<Preference>("mixedUsername"),
findPreference<Preference>("mixedPassword"),
findPreference<Preference>("appendHttpProxy"),
findPreference<Preference>("allowAccess")
)

fun updateMixedPrefsState(disabled: Boolean) {
mixedPrefsToToggle.forEach { it?.isEnabled = !disabled }
}

updateMixedPrefsState(disableMixed?.isChecked == true)

disableMixed?.setOnPreferenceChangeListener { _, newValue ->
updateMixedPrefsState(newValue as Boolean)
needReload()
true
}

mixedPort.onPreferenceChangeListener = reloadListener
mixedUsername.onPreferenceChangeListener = reloadListener
mixedPassword.onPreferenceChangeListener = reloadListener
//disableMixed.onPreferenceChangeListener = reloadListener
appendHttpProxy.onPreferenceChangeListener = reloadListener
showDirectSpeed.onPreferenceChangeListener = reloadListener
trafficSniffing.onPreferenceChangeListener = reloadListener
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/moe/matsuri/nb4a/utils/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,12 @@ object Util {
val encoded = match?.groupValues?.get(1) ?: ""
return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name())
}

fun generateCryptoSecurePassword(length: Int = 10): String {
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
val secureRandom = java.security.SecureRandom()
return (1..length)
.map { chars[secureRandom.nextInt(chars.length)] }
.joinToString("")
}
}
4 changes: 4 additions & 0 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
<string name="ads">Реклама</string>
<string name="allow_access">Разрешить подключения из локальной сети</string>
<string name="allow_access_sum">Привязать входящие серверы к 0.0.0.0</string>
<string name="proxy_username">Юзернейм прокси</string>
<string name="proxy_password">Пароль прокси</string>
<string name="disable_mixed">Отключить прокси SOCKS5</string>
<string name="disable_mixed_sum">Отключить прокси SOCKS5 для недопущения утечки внешнего IP сервера VPN</string>
<string name="allow_insecure">Разрешить небезопасное подключение</string>
<string name="allow_insecure_on_request_sum">Отключить проверку сертификатов при обновлении подписок</string>
<string name="allow_insecure_sum">Отключить проверку сертификата. При включении эта функция настолько же безопасна как передача пароля в открытом виде</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
<string name="cag_route">Route Settings</string>
<string name="allow_access">Allow Connections from the LAN</string>
<string name="allow_access_sum">Bind inbound servers to 0.0.0.0</string>
<string name="proxy_username">Proxy Username</string>
<string name="proxy_password">Proxy Password</string>
<string name="disable_mixed">Disable SOCKS5 proxy</string>
<string name="disable_mixed_sum">SOCKS5 proxy can be disabled to prevent VPN server external IP leak</string>
<string name="inbound_settings">Inbound Settings</string>
<string name="general_settings">App Settings</string>
<string name="require_http">Enable HTTP inbound</string>
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/res/xml/global_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@
app:key="mixedPort"
app:title="@string/port_proxy"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
app:defaultValue="User"
app:key="mixedUsername"
app:useSimpleSummaryProvider="true"
app:title="@string/proxy_username" />
<EditTextPreference
app:icon="@drawable/ic_settings_password"
app:key="mixedPassword"
app:useSimpleSummaryProvider="true"
app:title="@string/proxy_password" />
<SwitchPreference
app:defaultValue="false"
app:key="appendHttpProxy"
Expand All @@ -204,6 +214,11 @@
app:key="allowAccess"
app:summary="@string/allow_access_sum"
app:title="@string/allow_access" />
<SwitchPreference
app:defaultValue="false"
app:key="disableMixed"
app:summary="@string/disable_mixed_sum"
app:title="@string/disable_mixed" />
</PreferenceCategory>

<PreferenceCategory app:title="@string/cag_misc">
Expand Down