mirror of
https://github.com/wgtunnel/desktop.git
synced 2026-06-02 00:29:09 +02:00
fix: improve parser handling of script, amnezia sig packets, and preshared keys
This commit is contained in:
@@ -1078,6 +1078,344 @@
|
||||
"Apache-2.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"uniqueId": "commons-beanutils:commons-beanutils",
|
||||
"funding": [
|
||||
|
||||
],
|
||||
"developers": [
|
||||
{
|
||||
"name": "Geir Magnusson Jr."
|
||||
},
|
||||
{
|
||||
"name": "Gary Gregory"
|
||||
},
|
||||
{
|
||||
"name": "Stian Soiland-Reyes"
|
||||
},
|
||||
{
|
||||
"name": "Robert Burrell Donkin"
|
||||
},
|
||||
{
|
||||
"name": "Niall Pemberton"
|
||||
},
|
||||
{
|
||||
"name": "Scott Sanders"
|
||||
},
|
||||
{
|
||||
"name": "James Strachan"
|
||||
},
|
||||
{
|
||||
"name": "Tim O'Brien"
|
||||
},
|
||||
{
|
||||
"name": "Morgan James Delagrange"
|
||||
},
|
||||
{
|
||||
"name": "Yoav Shapira"
|
||||
},
|
||||
{
|
||||
"name": "Rob Tompkins"
|
||||
},
|
||||
{
|
||||
"name": "Stephen Colebourne"
|
||||
},
|
||||
{
|
||||
"name": "Craig McClanahan"
|
||||
},
|
||||
{
|
||||
"name": "James Carman"
|
||||
},
|
||||
{
|
||||
"name": "John E. Conlon"
|
||||
},
|
||||
{
|
||||
"name": "Martin van den Bemt"
|
||||
},
|
||||
{
|
||||
"name": "David Eric Pugh"
|
||||
},
|
||||
{
|
||||
"name": "Dion Gillard"
|
||||
},
|
||||
{
|
||||
"name": "Simon Kitching"
|
||||
},
|
||||
{
|
||||
"name": "Rodney Waldhoff"
|
||||
},
|
||||
{
|
||||
"name": "Benedikt Ritter"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "1.9.4",
|
||||
"description": "Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection.",
|
||||
"scm": {
|
||||
"connection": "scm:svn:http://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3",
|
||||
"url": "http://svn.apache.org/viewvc/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3",
|
||||
"developerConnection": "scm:svn:https://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3"
|
||||
},
|
||||
"name": "Apache Commons BeanUtils",
|
||||
"website": "https://commons.apache.org/proper/commons-beanutils/",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"organization": {
|
||||
"url": "https://www.apache.org/",
|
||||
"name": "The Apache Software Foundation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uniqueId": "commons-collections:commons-collections",
|
||||
"funding": [
|
||||
|
||||
],
|
||||
"developers": [
|
||||
{
|
||||
"name": "Stephen Colebourne"
|
||||
},
|
||||
{
|
||||
"name": "Arun M. Thomas"
|
||||
},
|
||||
{
|
||||
"name": "Craig McClanahan"
|
||||
},
|
||||
{
|
||||
"name": "James Carman"
|
||||
},
|
||||
{
|
||||
"name": "Morgan Delagrange"
|
||||
},
|
||||
{
|
||||
"name": "Henri Yandell"
|
||||
},
|
||||
{
|
||||
"name": "Geir Magnusson"
|
||||
},
|
||||
{
|
||||
"name": "Matthew Hawthorne"
|
||||
},
|
||||
{
|
||||
"name": "Phil Steitz"
|
||||
},
|
||||
{
|
||||
"name": "Robert Burrell Donkin"
|
||||
},
|
||||
{
|
||||
"name": "Rodney Waldhoff"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "3.2.2",
|
||||
"description": "Types that extend and augment the Java Collections Framework.",
|
||||
"scm": {
|
||||
"connection": "scm:svn:http://svn.apache.org/repos/asf/commons/proper/collections/trunk",
|
||||
"url": "http://svn.apache.org/viewvc/commons/proper/collections/trunk",
|
||||
"developerConnection": "scm:svn:https://svn.apache.org/repos/asf/commons/proper/collections/trunk"
|
||||
},
|
||||
"name": "Apache Commons Collections",
|
||||
"website": "http://commons.apache.org/collections/",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"organization": {
|
||||
"url": "http://www.apache.org/",
|
||||
"name": "The Apache Software Foundation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uniqueId": "commons-digester:commons-digester",
|
||||
"funding": [
|
||||
|
||||
],
|
||||
"developers": [
|
||||
{
|
||||
"name": "Craig McClanahan"
|
||||
},
|
||||
{
|
||||
"name": "Tim OBrien"
|
||||
},
|
||||
{
|
||||
"name": "Rahul Akolkar"
|
||||
},
|
||||
{
|
||||
"name": "Robert Burrell Donkin"
|
||||
},
|
||||
{
|
||||
"name": "Jean-Francois Arcand"
|
||||
},
|
||||
{
|
||||
"name": "Scott Sanders"
|
||||
},
|
||||
{
|
||||
"name": "James Strachan"
|
||||
},
|
||||
{
|
||||
"name": "Jason van Zyl"
|
||||
},
|
||||
{
|
||||
"name": "Simon Kitching"
|
||||
},
|
||||
{
|
||||
"name": "Simone Tripodi"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "2.1",
|
||||
"description": "The Digester package lets you configure an XML to Java object mapping module\n which triggers certain actions called rules whenever a particular \n pattern of nested XML elements is recognized.",
|
||||
"scm": {
|
||||
"connection": "scm:svn:http://svn.apache.org/repos/asf/commons/proper/digester/tags/DIGESTER_2_1_RC2",
|
||||
"url": "http://svn.apache.org/viewvc/commons/proper/digester/tags/DIGESTER_2_1_RC2",
|
||||
"developerConnection": "scm:svn:https://svn.apache.org/repos/asf/commons/proper/digester/tags/DIGESTER_2_1_RC2"
|
||||
},
|
||||
"name": "Commons Digester",
|
||||
"website": "http://commons.apache.org/digester/",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"organization": {
|
||||
"url": "http://www.apache.org/",
|
||||
"name": "The Apache Software Foundation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uniqueId": "commons-logging:commons-logging",
|
||||
"funding": [
|
||||
|
||||
],
|
||||
"developers": [
|
||||
{
|
||||
"name": "Juozas Baliuka"
|
||||
},
|
||||
{
|
||||
"name": "Costin Manolache"
|
||||
},
|
||||
{
|
||||
"name": "Dennis Lundberg"
|
||||
},
|
||||
{
|
||||
"name": "Brian Stansberry"
|
||||
},
|
||||
{
|
||||
"name": "Robert Burrell Donkin"
|
||||
},
|
||||
{
|
||||
"name": "Scott Sanders"
|
||||
},
|
||||
{
|
||||
"name": "Thomas Neidhart"
|
||||
},
|
||||
{
|
||||
"name": "Richard Sitze"
|
||||
},
|
||||
{
|
||||
"organisationUrl": "https://www.apache.org/",
|
||||
"name": "Gary Gregory"
|
||||
},
|
||||
{
|
||||
"name": "Craig McClanahan"
|
||||
},
|
||||
{
|
||||
"name": "Morgan Delagrange"
|
||||
},
|
||||
{
|
||||
"name": "Peter Donald"
|
||||
},
|
||||
{
|
||||
"name": "Simon Kitching"
|
||||
},
|
||||
{
|
||||
"name": "Rodney Waldhoff"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "1.3.0",
|
||||
"description": "Apache Commons Logging is a thin adapter allowing configurable bridging to other,\n well known logging systems.",
|
||||
"scm": {
|
||||
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-logging",
|
||||
"url": "https://gitbox.apache.org/repos/asf/commons-logging",
|
||||
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf/commons-logging"
|
||||
},
|
||||
"name": "Apache Commons Logging",
|
||||
"website": "https://commons.apache.org/proper/commons-logging/",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"organization": {
|
||||
"url": "https://www.apache.org/",
|
||||
"name": "The Apache Software Foundation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uniqueId": "commons-validator:commons-validator",
|
||||
"funding": [
|
||||
|
||||
],
|
||||
"developers": [
|
||||
{
|
||||
"name": "Martin Cooper"
|
||||
},
|
||||
{
|
||||
"name": "James Mitchell"
|
||||
},
|
||||
{
|
||||
"name": "James Turner"
|
||||
},
|
||||
{
|
||||
"name": "David Graham"
|
||||
},
|
||||
{
|
||||
"name": "Rob Leland"
|
||||
},
|
||||
{
|
||||
"name": "Henri Yandell"
|
||||
},
|
||||
{
|
||||
"name": "Ben Speakmon"
|
||||
},
|
||||
{
|
||||
"name": "Niall Pemberton"
|
||||
},
|
||||
{
|
||||
"organisationUrl": "https://www.apache.org/",
|
||||
"name": "Gary Gregory"
|
||||
},
|
||||
{
|
||||
"name": "Ted Husted"
|
||||
},
|
||||
{
|
||||
"name": "Craig McClanahan"
|
||||
},
|
||||
{
|
||||
"name": "David Winterfeldt"
|
||||
},
|
||||
{
|
||||
"name": "SimoneTripodi"
|
||||
},
|
||||
{
|
||||
"name": "Don Brown"
|
||||
},
|
||||
{
|
||||
"name": "Nick Burch"
|
||||
},
|
||||
{
|
||||
"name": "Benedikt Ritter"
|
||||
}
|
||||
],
|
||||
"artifactVersion": "1.8.0",
|
||||
"description": "Apache Commons Validator provides the building blocks for both client side validation and server side data validation.\n It may be used standalone or with a framework like Struts.",
|
||||
"scm": {
|
||||
"connection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator",
|
||||
"url": "https://gitbox.apache.org/repos/asf/commons-validator",
|
||||
"developerConnection": "scm:git:https://gitbox.apache.org/repos/asf/commons-validator"
|
||||
},
|
||||
"name": "Apache Commons Validator",
|
||||
"website": "http://commons.apache.org/proper/commons-validator/",
|
||||
"licenses": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"organization": {
|
||||
"url": "https://www.apache.org/",
|
||||
"name": "The Apache Software Foundation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uniqueId": "de.jangassen:jfa",
|
||||
"funding": [
|
||||
|
||||
@@ -52,6 +52,7 @@ androidx-sqlite = "2.6.2"
|
||||
lang3 = "3.20.0"
|
||||
humanReadable = "1.12.3"
|
||||
datetime = "0.7.1"
|
||||
commonsValidator = "1.10.1"
|
||||
|
||||
[bundles]
|
||||
ktor-client-jvm = ["ktor-client-core-jvm", "ktor-client-cio-jvm", "ktor-client-content-negotiation-jvm", "ktor-serialization-json-jvm", "ktor-client-okhttp", "ktor-client-websockets-jvm"]
|
||||
@@ -188,6 +189,7 @@ multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", vers
|
||||
|
||||
human-readable = { module = "nl.jacobras:Human-Readable", version.ref = "humanReadable" }
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
|
||||
commons-validator = { module = "commons-validator:commons-validator", version.ref = "commonsValidator" }
|
||||
|
||||
|
||||
[plugins]
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencies {
|
||||
|
||||
implementation(libs.human.readable)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
implementation(libs.commons.validator)
|
||||
}
|
||||
|
||||
tasks.test { useJUnitPlatform() }
|
||||
@@ -29,7 +30,7 @@ publishing {
|
||||
register<MavenPublication>("release") {
|
||||
groupId = "com.zaneschepke.wireguardautotunnel"
|
||||
artifactId = "amneziawg-parser"
|
||||
version = "1.0.1"
|
||||
version = "1.0.6"
|
||||
from(components["java"])
|
||||
pom {
|
||||
name.set("AmneziaWG Parser")
|
||||
|
||||
@@ -93,7 +93,12 @@ data class ActiveConfig(val interfaceSection: InterfaceSection, val peers: List<
|
||||
}
|
||||
|
||||
return ActiveConfig(
|
||||
interfaceSection = Config.buildInterface(interfaceMap, emptyList()),
|
||||
interfaceSection =
|
||||
Config.buildInterface(
|
||||
interfaceMap,
|
||||
InterfaceScriptsBuilder.InterfaceScripts(),
|
||||
emptyList(),
|
||||
),
|
||||
peers = peerMaps.map { Config.buildActivePeer(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ data class Config(
|
||||
|
||||
companion object {
|
||||
fun parseQuickString(configString: String): Config {
|
||||
val scripts = InterfaceScriptsBuilder()
|
||||
val interfaceMap = mutableMapOf<String, String>()
|
||||
val peerMaps = mutableListOf<Pair<MutableMap<String, String>, List<String>>>()
|
||||
|
||||
@@ -89,9 +90,23 @@ data class Config(
|
||||
}
|
||||
|
||||
val parts = raw.split("=", limit = 2)
|
||||
|
||||
if (parts.size == 2) {
|
||||
val key = parts[0].trim()
|
||||
var value = parts[1].trim()
|
||||
|
||||
if (currentSectionMap === interfaceMap) {
|
||||
when (key) {
|
||||
"PreUp",
|
||||
"PostUp",
|
||||
"PreDown",
|
||||
"PostDown" -> {
|
||||
scripts.add(key, value)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove whitespaces
|
||||
if (
|
||||
key in
|
||||
@@ -99,6 +114,7 @@ data class Config(
|
||||
"PrivateKey",
|
||||
"PublicKey",
|
||||
"PresharedKey",
|
||||
"PreSharedKey",
|
||||
"H1",
|
||||
"H2",
|
||||
"H3",
|
||||
@@ -113,12 +129,16 @@ data class Config(
|
||||
|
||||
return Config(
|
||||
headerComments = headerComments,
|
||||
`interface` = buildInterface(interfaceMap, interfaceComments),
|
||||
`interface` = buildInterface(interfaceMap, scripts.build(), interfaceComments),
|
||||
peers = peerMaps.map { (map, comments) -> buildPeer(map, comments) },
|
||||
)
|
||||
}
|
||||
|
||||
internal fun buildInterface(m: Map<String, String>, comments: List<String>) =
|
||||
internal fun buildInterface(
|
||||
m: Map<String, String>,
|
||||
scripts: InterfaceScriptsBuilder.InterfaceScripts,
|
||||
comments: List<String>,
|
||||
) =
|
||||
InterfaceSection(
|
||||
comments = comments,
|
||||
privateKey = m["PrivateKey"] ?: "",
|
||||
@@ -129,6 +149,10 @@ data class Config(
|
||||
fwMark = m.getInt("FwMark", "Interface"),
|
||||
table = m["Table"],
|
||||
saveConfig = m.getBool("SaveConfig", "Interface"),
|
||||
preUp = scripts.preUp,
|
||||
postUp = scripts.postUp,
|
||||
preDown = scripts.preDown,
|
||||
postDown = scripts.postDown,
|
||||
jC = m.getInt("Jc", "Interface"),
|
||||
jMin = m.getInt("Jmin", "Interface"),
|
||||
jMax = m.getInt("Jmax", "Interface"),
|
||||
@@ -154,7 +178,7 @@ data class Config(
|
||||
publicKey = m["PublicKey"] ?: "",
|
||||
allowedIPs = m["AllowedIPs"],
|
||||
endpoint = m["Endpoint"],
|
||||
presharedKey = m["PresharedKey"],
|
||||
presharedKey = m["PresharedKey"] ?: m["PreSharedKey"],
|
||||
persistentKeepalive = m.getInt("PersistentKeepalive", "Peer"),
|
||||
comments = comments,
|
||||
)
|
||||
@@ -194,7 +218,7 @@ data class Config(
|
||||
publicKey = m["PublicKey"] ?: "",
|
||||
allowedIPs = m["AllowedIPs"],
|
||||
endpoint = m["Endpoint"],
|
||||
presharedKey = m["PresharedKey"],
|
||||
presharedKey = m["PresharedKey"] ?: m["PreSharedKey"],
|
||||
persistentKeepalive = m.getInt("PersistentKeepalive", "Peer"),
|
||||
lastHandshakeSeconds = m.getLong("LastHandshakeSeconds", "Peer"),
|
||||
lastHandshakeNanos = m.getLong("LastHandshakeNanos", "Peer"),
|
||||
@@ -202,8 +226,8 @@ data class Config(
|
||||
rxBytes = m.getLong("RxBytes", "Peer"),
|
||||
)
|
||||
|
||||
internal fun generatePublicKeyFromPrivate(privateBase64: String): String {
|
||||
val privateKey = Key.fromBase64(privateBase64)
|
||||
fun generatePublicKeyFromPrivateKey(privateKeyBase64: String): String {
|
||||
val privateKey = Key.fromBase64(privateKeyBase64)
|
||||
val publicKey = Key.generatePublicKey(privateKey)
|
||||
return publicKey.toBase64()
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ enum class ErrorType {
|
||||
INVALID_PORT_RANGE,
|
||||
INVALID_MTU_RANGE,
|
||||
INVALID_FWMARK,
|
||||
INVALID_JC_RANGE,
|
||||
INVALID_JC_VALUE,
|
||||
INVALID_JMIN_JMAX_ORDER,
|
||||
INVALID_JMAX_MTU,
|
||||
INVALID_PADDING_NEGATIVE,
|
||||
INVALID_HEADER_FORMAT,
|
||||
INVALID_SIGNATURE_FORMAT,
|
||||
INVALID_ENDPOINT_FORMAT,
|
||||
INVALID_KEEPALIVE_NEGATIVE,
|
||||
INVALID_KEEPALIVE_VALUE,
|
||||
INVALID_CIDR,
|
||||
INVALID_IP,
|
||||
INVALID_HOSTNAME,
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.zaneschepke.wireguardautotunnel.parser
|
||||
|
||||
class InterfaceScriptsBuilder {
|
||||
|
||||
private val preUp = mutableListOf<String>()
|
||||
private val postUp = mutableListOf<String>()
|
||||
private val preDown = mutableListOf<String>()
|
||||
private val postDown = mutableListOf<String>()
|
||||
|
||||
fun add(key: String, value: String) {
|
||||
when (key) {
|
||||
"PreUp" -> preUp += value
|
||||
"PostUp" -> postUp += value
|
||||
"PreDown" -> preDown += value
|
||||
"PostDown" -> postDown += value
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): InterfaceScripts =
|
||||
InterfaceScripts(
|
||||
preUp = preUp.toList(),
|
||||
postUp = postUp.toList(),
|
||||
preDown = preDown.toList(),
|
||||
postDown = postDown.toList(),
|
||||
)
|
||||
|
||||
data class InterfaceScripts(
|
||||
val preUp: List<String> = emptyList(),
|
||||
val postUp: List<String> = emptyList(),
|
||||
val preDown: List<String> = emptyList(),
|
||||
val postDown: List<String> = emptyList(),
|
||||
)
|
||||
}
|
||||
+4
-12
@@ -92,14 +92,11 @@ data class InterfaceSection(
|
||||
}
|
||||
|
||||
jC?.let {
|
||||
if (it !in 4..12)
|
||||
throw ConfigParseException(ErrorType.INVALID_JC_RANGE, "Interface.Jc", it)
|
||||
if (it < 0) throw ConfigParseException(ErrorType.INVALID_JC_VALUE, "Interface.Jc", it)
|
||||
}
|
||||
if (jMin != null && jMax != null) {
|
||||
if (jMin > jMax)
|
||||
throw ConfigParseException(ErrorType.INVALID_JMIN_JMAX_ORDER, "Interface.Jmin/Jmax")
|
||||
if (jMax >= (mtu ?: 1500))
|
||||
throw ConfigParseException(ErrorType.INVALID_JMAX_MTU, "Interface.Jmax", jMax)
|
||||
}
|
||||
|
||||
listOf(s1, s2, s3, s4).forEachIndexed { i, s ->
|
||||
@@ -121,14 +118,9 @@ data class InterfaceSection(
|
||||
}
|
||||
}
|
||||
|
||||
listOf(i1, i2, i3, i4, i5).forEachIndexed { i, sig ->
|
||||
if (sig != null && !NetworkUtils.isValidHexSignature(sig)) {
|
||||
throw ConfigParseException(
|
||||
ErrorType.INVALID_SIGNATURE_FORMAT,
|
||||
"Interface.I${i + 1}",
|
||||
sig,
|
||||
)
|
||||
}
|
||||
listOf(i1 to "I1", i2 to "I2", i3 to "I3", i4 to "I4", i5 to "I5").forEach {
|
||||
(sig, shortName) ->
|
||||
sig?.let { NetworkUtils.validateAmneziaSignaturePacket(it, "Interface.$shortName") }
|
||||
}
|
||||
|
||||
address
|
||||
|
||||
@@ -25,7 +25,7 @@ data class PeerSection(
|
||||
persistentKeepalive?.let {
|
||||
if (it !in 0..65535)
|
||||
throw ConfigParseException(
|
||||
ErrorType.INVALID_KEEPALIVE_NEGATIVE,
|
||||
ErrorType.INVALID_KEEPALIVE_VALUE,
|
||||
"$prefix.PersistentKeepalive",
|
||||
it,
|
||||
)
|
||||
@@ -34,7 +34,7 @@ data class PeerSection(
|
||||
endpoint?.let {
|
||||
val (host, portStr) = Config.parseEndpoint(it)
|
||||
val port = portStr?.toIntOrNull()
|
||||
if (host == null || port == null || port !in 0..65535) {
|
||||
if (host == null || port == null || port !in 1..65535) {
|
||||
throw ConfigParseException(
|
||||
ErrorType.INVALID_ENDPOINT_FORMAT,
|
||||
"$prefix.Endpoint",
|
||||
@@ -50,6 +50,12 @@ data class PeerSection(
|
||||
}
|
||||
}
|
||||
|
||||
presharedKey?.let {
|
||||
if (!NetworkUtils.isValidBase64(it)) {
|
||||
throw ConfigParseException(ErrorType.INVALID_BASE64_KEY, "$prefix.PresharedKey", it)
|
||||
}
|
||||
}
|
||||
|
||||
allowedIPs
|
||||
?.split(",")
|
||||
?.map { it.trim() }
|
||||
|
||||
+6
@@ -20,6 +20,12 @@ object ConfigFormatter {
|
||||
sb.appendLine("PrivateKey = ${if (hidePrivateKey) "(hidden)" else iface.privateKey}")
|
||||
iface.address?.let { sb.appendLine("Address = $it") }
|
||||
iface.dns?.let { sb.appendLine("DNS = $it") }
|
||||
|
||||
iface.preUp?.forEach { sb.appendLine("PreUp = $it") }
|
||||
iface.postUp?.forEach { sb.appendLine("PostUp = $it") }
|
||||
iface.preDown?.forEach { sb.appendLine("PreDown = $it") }
|
||||
iface.postDown?.forEach { sb.appendLine("PostDown = $it") }
|
||||
|
||||
iface.listenPort?.let { sb.appendLine("ListenPort = $it") }
|
||||
iface.mtu?.let { sb.appendLine("MTU = $it") }
|
||||
iface.fwMark?.let { sb.appendLine("FwMark = $it") }
|
||||
|
||||
+110
-17
@@ -1,22 +1,21 @@
|
||||
package com.zaneschepke.wireguardautotunnel.parser.util
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.parser.ConfigParseException
|
||||
import com.zaneschepke.wireguardautotunnel.parser.ErrorType
|
||||
import java.net.InetAddress
|
||||
import org.apache.commons.validator.routines.InetAddressValidator
|
||||
|
||||
object NetworkUtils {
|
||||
|
||||
private val validator = InetAddressValidator.getInstance()
|
||||
|
||||
private val hostnameRegex =
|
||||
Regex(
|
||||
"^(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])*$"
|
||||
)
|
||||
|
||||
fun isValidIp(ip: String): Boolean {
|
||||
val sanitized = ip.removeSurrounding("[", "]")
|
||||
if (sanitized.any { it.lowercaseChar() in 'g'..'z' }) return false
|
||||
|
||||
return try {
|
||||
InetAddress.getAllByName(sanitized).isNotEmpty()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
return validator.isValid(ip.removeSurrounding("[", "]"))
|
||||
}
|
||||
|
||||
fun isValidCidr(cidr: String): Boolean {
|
||||
@@ -41,7 +40,6 @@ object NetworkUtils {
|
||||
|
||||
fun isValidDnsEntry(entry: String): Boolean {
|
||||
if (entry.isBlank()) return false
|
||||
// Safe: isValidIp is offline, isValidHostname is regex.
|
||||
return isValidIp(entry) || isValidHostname(entry)
|
||||
}
|
||||
|
||||
@@ -51,9 +49,12 @@ object NetworkUtils {
|
||||
}
|
||||
|
||||
fun isValidBase64(str: String): Boolean {
|
||||
if (str.length != 44 || !str.endsWith("=")) return false
|
||||
val base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
||||
return str.all { it in base64Chars }
|
||||
return try {
|
||||
val decoded = kotlin.io.encoding.Base64.decode(str)
|
||||
decoded.size == 32
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun isValidAmneziaHeader(header: String): Boolean {
|
||||
@@ -73,10 +74,102 @@ object NetworkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
fun isValidHexSignature(signature: String): Boolean {
|
||||
val hex = signature.removePrefix("0x").trim()
|
||||
if (hex.isEmpty() || hex.length % 2 != 0) return false
|
||||
val hexChars = "0123456789abcdefABCDEF"
|
||||
return hex.all { it in hexChars }
|
||||
@Throws(ConfigParseException::class)
|
||||
fun validateAmneziaSignaturePacket(value: String, fieldName: String) {
|
||||
if (value.isBlank()) {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
|
||||
var index = 0
|
||||
|
||||
// every tag mush start with <
|
||||
while (index < value.length) {
|
||||
if (value[index] != '<') {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
index++
|
||||
|
||||
val typeStart = index
|
||||
while (index < value.length && value[index].isLetter()) {
|
||||
index++
|
||||
}
|
||||
val tagType = value.substring(typeStart, index).lowercase()
|
||||
|
||||
if (tagType.isEmpty()) {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
|
||||
// All tags except <t> require a space
|
||||
if (tagType != "t") {
|
||||
if (index >= value.length || value[index] != ' ') {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
when (tagType) {
|
||||
"b" -> index = parseStaticBytesTag(value, index, fieldName)
|
||||
"r",
|
||||
"rd",
|
||||
"rc" -> index = parseRandomTag(value, index, fieldName)
|
||||
"t" -> {} // timestamp has no parameter
|
||||
else ->
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
|
||||
// every tag must end with >
|
||||
if (index >= value.length || value[index] != '>') {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStaticBytesTag(value: String, start: Int, fieldName: String): Int {
|
||||
var index = start
|
||||
|
||||
// must start with 0x
|
||||
if (
|
||||
index + 2 > value.length ||
|
||||
!value.substring(index, index + 2).equals("0x", ignoreCase = true)
|
||||
) {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
index += 2
|
||||
|
||||
val hexStart = index
|
||||
while (index < value.length && value[index].isHexDigit()) {
|
||||
index++
|
||||
}
|
||||
|
||||
// must be a valid hex
|
||||
val hexLength = index - hexStart
|
||||
if (hexLength == 0 || hexLength % 2 != 0) {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private fun parseRandomTag(value: String, start: Int, fieldName: String): Int {
|
||||
var index = start
|
||||
val numStart = index
|
||||
while (index < value.length && value[index].isDigit()) {
|
||||
index++
|
||||
}
|
||||
|
||||
// must have at least one digit
|
||||
if (index == numStart) {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
|
||||
// make sure it is a positive number
|
||||
val size = value.substring(numStart, index).toIntOrNull()
|
||||
if (size == null || size <= 0) {
|
||||
throw ConfigParseException(ErrorType.INVALID_SIGNATURE_FORMAT, fieldName, value)
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private fun Char.isHexDigit(): Boolean =
|
||||
this in '0'..'9' || this in 'a'..'f' || this in 'A'..'F'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user