From affac839cb313e5bfb2118e190fbf0ba7d22cfd4 Mon Sep 17 00:00:00 2001 From: WiseDragoon5374 <93832641+WiseDragoon5374@users.noreply.github.com> Date: Wed, 7 Jun 2023 14:46:07 -0400 Subject: [PATCH 01/12] Fix headers in integration readme (#3623) --- src/main/scala/li/cil/oc/integration/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/li/cil/oc/integration/README.md b/src/main/scala/li/cil/oc/integration/README.md index ed4c129fe2..f5cd070ea3 100644 --- a/src/main/scala/li/cil/oc/integration/README.md +++ b/src/main/scala/li/cil/oc/integration/README.md @@ -1,6 +1,6 @@ This package contains code use to integrate with other mods. This is usually done by implementing block drivers for other mods' blocks, or by implementing (item stack) converters. -###General Structure +### General Structure The general structure for mod integration is as follows: - All mods' IDs are defined in `Mods.IDs` (`Mods.scala` file). - For most mods, a `SimpleMod` instance suffices, some may require a specialized implementation. These instances are an internal way of checking for mod availablity. @@ -9,7 +9,7 @@ The general structure for mod integration is as follows: Have a look at the existing modules for examples if that description was too abstract for you. -###On pull requests +### On pull requests The basic guidelines from the main readme still apply, but I'd like to stress again that all integration must be *optional*. Make sure you properly test OC still works with and without the mod you added support for. An additional guideline is on what drivers should actually *do*. Drivers built into OC should, in general, err on the side of being limited. This way addons can still add more "powerful" functionality, if so desired, while the other way around would not work (addons would not be able to limit existing functionality). Here's a few rules-of-thumb: @@ -19,4 +19,4 @@ An additional guideline is on what drivers should actually *do*. Drivers built i - Drivers and converters should avoid exposing "implementation detail". This includes things such as actual block and item ids, for example. - If there is an upgrade for it, don't write a driver for it. If you're up to it, adjust the upgrade to work in the adapter, otherwise let me know and I'll have a look. -When in doubt, ask on the IRC or open an issue to discuss the driver you'd like to add! \ No newline at end of file +When in doubt, ask on the IRC or open an issue to discuss the driver you'd like to add! From 5b2ba76a4c242b369b9b6ac6196fd04d96580ad0 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 10 Jun 2023 00:22:26 +0200 Subject: [PATCH 02/12] Use HTTPS for deps (again), close #3625 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ca548b3232..68607e40cc 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } maven { name = "gt" - url = "http://gregtech.overminddl1.com/" + url = "https://gregtech.overminddl1.com/" } maven { name = "sonatype" @@ -107,7 +107,7 @@ repositories { } ivy { name 'asie dependency mirror' - artifactPattern "http://asie.pl/javadeps/[module]-[revision](-[classifier]).[ext]" + artifactPattern "https://asie.pl/javadeps/[module]-[revision](-[classifier]).[ext]" metadataSources { artifact() } From 0c1763270a108b54d158969c516a38d7e7c60b83 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 10 Jun 2023 00:22:36 +0200 Subject: [PATCH 03/12] Use HTTPS for deps (again), close #3625 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 46dfd95706..ad250a8668 100644 --- a/build.gradle +++ b/build.gradle @@ -153,7 +153,7 @@ repositories { } ivy { name 'asie dependency mirror' - artifactPattern "http://asie.pl/javadeps/[module]-[revision](-[classifier]).[ext]" + artifactPattern "https://asie.pl/javadeps/[module]-[revision](-[classifier]).[ext]" metadataSources { artifact() } From 8e80453321594bf48359c2c1a747dd8cbb825184 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 9 Jun 2023 19:31:55 -0400 Subject: [PATCH 04/12] Get tests running again Signed-off-by: Jonathan Leitschuh --- build.gradle | 1 + src/test/scala/NetworkTest.scala | 3 +++ 2 files changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 68607e40cc..97151ba3e1 100644 --- a/build.gradle +++ b/build.gradle @@ -165,6 +165,7 @@ dependencies { embedded name: 'OC-JNLua', version: '20230530.0', ext: 'jar' embedded name: 'OC-JNLua-Natives', version: '20220928.1', ext: 'jar' + testCompile "junit:junit:4.13" testCompile "org.mockito:mockito-all:1.10.19" testCompile "org.scalactic:scalactic_2.11:2.2.6" testCompile "org.scalatest:scalatest_2.11:2.2.6" diff --git a/src/test/scala/NetworkTest.scala b/src/test/scala/NetworkTest.scala index 73d6839e19..22255d4800 100644 --- a/src/test/scala/NetworkTest.scala +++ b/src/test/scala/NetworkTest.scala @@ -5,11 +5,14 @@ import li.cil.oc.api.network.Node import li.cil.oc.api.network.Visibility import li.cil.oc.server.network.Network import li.cil.oc.server.network.{Node => MutableNode} +import org.junit.runner.RunWith import org.scalatest._ +import org.scalatest.junit.JUnitRunner import org.scalatest.mock.MockitoSugar import scala.collection.convert.WrapAsScala._ +@RunWith(classOf[JUnitRunner]) class NetworkTest extends FlatSpec with MockitoSugar { Network.isServer = () => true api.API.network = Network From 9013fc202846aeb06f859b2a1fe49a19442c55ae Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sat, 24 Jun 2023 19:06:34 +0200 Subject: [PATCH 05/12] fix #3634 --- src/main/scala/li/cil/oc/server/component/InternetCard.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/li/cil/oc/server/component/InternetCard.scala b/src/main/scala/li/cil/oc/server/component/InternetCard.scala index 3e8dd1fe42..2b15bf2ade 100644 --- a/src/main/scala/li/cil/oc/server/component/InternetCard.scala +++ b/src/main/scala/li/cil/oc/server/component/InternetCard.scala @@ -209,7 +209,7 @@ object InternetCard { if(readableKeys.nonEmpty) { val newSelector = Selector.open() - selectedKeys.filter(!readableKeys.contains(_)).foreach(key => { + selector.keys.filter(!readableKeys.contains(_)).foreach(key => { key.channel.register(newSelector, SelectionKey.OP_READ, key.attachment) }) selector.close() From 77649fd246f3ddba5cd6ba61e15bbc5952ea0a08 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Wed, 28 Jun 2023 15:16:02 +0200 Subject: [PATCH 06/12] propose fix for #3635 --- .../scala/li/cil/oc/common/tileentity/Rack.scala | 14 +++++++------- .../scala/li/cil/oc/server/PacketHandler.scala | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala index e9a16327d2..e85fb88aed 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Rack.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Rack.scala @@ -59,36 +59,36 @@ class Rack extends traits.PowerAcceptor with traits.Hub with traits.PowerBalance case _ => None } - val oldSide = nodeMapping(slot)(connectableIndex) + val oldSide = nodeMapping(slot)(connectableIndex + 1) if (oldSide == newSide) return // Cut connection / remove sniffer node. val mountable = getMountable(slot) if (mountable != null && oldSide.isDefined) { - if (connectableIndex == 0) { + if (connectableIndex == -1) { val node = mountable.node val plug = sidedNode(toGlobal(oldSide.get)) if (node != null && plug != null) { node.disconnect(plug) } } - else { + else if (connectableIndex >= 0) { snifferNodes(slot)(connectableIndex).remove() } } - nodeMapping(slot)(connectableIndex) = newSide + nodeMapping(slot)(connectableIndex + 1) = newSide // Establish connection / add sniffer node. if (mountable != null && newSide.isDefined) { - if (connectableIndex == 0) { + if (connectableIndex == -1) { val node = mountable.node val plug = sidedNode(toGlobal(newSide.get)) if (node != null && plug != null) { node.connect(plug) } } - else if (connectableIndex < mountable.getConnectableCount) { + else if (connectableIndex >= 0 && connectableIndex < mountable.getConnectableCount) { val connectable = mountable.getConnectableAt(connectableIndex) if (connectable != null && connectable.node != null) { if (connectable.node.network == null) { @@ -114,7 +114,7 @@ class Rack extends traits.PowerAcceptor with traits.Hub with traits.PowerBalance case _ => // Not connected to this side. } for (connectableIndex <- 0 until 3) { - mapping(connectableIndex) match { + mapping(connectableIndex + 1) match { case Some(side) if toGlobal(side) == plugSide => val mountable = getMountable(slot) if (mountable != null && connectableIndex < mountable.getConnectableCount) { diff --git a/src/main/scala/li/cil/oc/server/PacketHandler.scala b/src/main/scala/li/cil/oc/server/PacketHandler.scala index a54ab678e4..6d7deb06cd 100644 --- a/src/main/scala/li/cil/oc/server/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/server/PacketHandler.scala @@ -252,7 +252,7 @@ object PacketHandler extends CommonPacketHandler { entity match { case Some(t) => p.player match { case player: EntityPlayerMP if t.isUseableByPlayer(player) => - t.connect(mountableIndex, nodeIndex, side) + t.connect(mountableIndex, nodeIndex - 1, side) case _ => } case _ => // Invalid packet. From 1b7761f4cedf4a2ccaf6400d48fbd3fb72218b37 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Wed, 28 Jun 2023 15:17:00 +0200 Subject: [PATCH 07/12] update changelog --- changelog.md | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index cb7354d842..24fd723e0a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,30 +1,8 @@ -## New features - -* [#3533] Added support for observing the contents of fluid container items. -* [1.12.2] Ported some CoFH Core, Ender IO and Railcraft drivers and wrench support. -* Added Railcraft Anchor/Worldspike driver (repo-alt). -* Added Spanish translation (sanmofe). - ## Fixes/improvements -* [#3620] Fixed OC 1.8.0+ regression involving API arguments and numbers. -* [#3013] Fixed rare server-side deadlock when sending disk activity update packets. -* Fixed bugs in internal wcwidth() implementation and updated it to cover Unicode 12. -* [1.7.10] Fixed the Database upgrade's documentation not showing up in NEI. -* Fixed server->client synchronization for some types of GPU bitblt operations. -* Fixed string.gmatch not supporting the "init" argument on Lua 5.4. -* Tweaks to server->client networking code: - * Added support for configuring the maximum packet distance for effects, sounds, and all client packets. - * Improved the method of synchronizing tile entity updates with the client. - * Robot light colors are now sent to all observers of the tile entity, preventing a potential (rare) glitch. -* Update GNU Unifont to 15.0.05. - -## OpenOS fixes/improvements - -* [#3371] Fix minor bug in rm.lua. -* Fix "ls -l" command on Lua 5.4. -* General minor improvements to the codebase. +* [#3635] ArrayIndexOutOfBoundsException when using servers with 3 network cards +* [#3634] Internet card selector update logic erroneously drops non-ready keys ## List of contributors -asie, ds84182, Possseidon, repo-alt, sanmofe +asie, Fingercomp From f275e0d44ae9bac643c94f78c3a605b9426fcdea Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Wed, 28 Jun 2023 15:26:45 +0200 Subject: [PATCH 08/12] propose fix for #3628 --- changelog.md | 8 ++++++-- .../assets/opencomputers/loot/network/data/bin/ping.lua | 6 +++--- .../assets/opencomputers/loot/openos/boot/02_os.lua | 2 +- .../assets/opencomputers/loot/openos/lib/core/boot.lua | 2 +- src/main/resources/assets/opencomputers/lua/machine.lua | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 24fd723e0a..404a6d10d7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,11 @@ ## Fixes/improvements -* [#3635] ArrayIndexOutOfBoundsException when using servers with 3 network cards -* [#3634] Internet card selector update logic erroneously drops non-ready keys +* [#3635] Fix ArrayIndexOutOfBoundsException when using servers with 3 network cards +* [#3634] Fix Internet card selector update logic erroneously dropping non-ready keys + +## OpenOS fixes/improvements + +* [#3628] Fix jitter when using os.sleep() ## List of contributors diff --git a/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua b/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua index 6ba9fc7312..2801a1311b 100644 --- a/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua +++ b/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua @@ -51,7 +51,7 @@ local function doSleep() local deadline = computer.uptime() + (tonumber(options.i) or tonumber(options.interval) or 1) repeat event.pull(deadline - computer.uptime()) - until computer.uptime() >= deadline + until (computer.uptime() - deadline) >= -0.001 end local function doPing() @@ -66,9 +66,9 @@ local function doPing() local e, replier, id, inpayload repeat e, replier, id, inpayload = event.pull(deadline - computer.uptime(), "ping_reply") - until computer.uptime() >= deadline or (e == "ping_reply" and id == icmp_seq) + until ((computer.uptime() - deadline) >= -0.001) or (e == "ping_reply" and id == icmp_seq) - if computer.uptime() >= deadline and e ~= "ping_reply" then + if ((computer.uptime() - deadline) >= -0.001) and e ~= "ping_reply" then verbose(tostring(len).." bytes lost: icmp_seq="..tostring(icmp_seq)) elseif inpayload == payload then stats.received = stats.received + 1 diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua index 314635696a..d193f94809 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua @@ -27,7 +27,7 @@ function os.sleep(timeout) local deadline = computer.uptime() + (timeout or 0) repeat event.pull(deadline - computer.uptime()) - until computer.uptime() >= deadline + until (computer.uptime() - deadline) >= -0.001 end os.setenv("PATH", "/bin:/usr/bin:/home/bin:.") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua index 2955da2921..c18cc4c951 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua @@ -1,7 +1,7 @@ -- called from /init.lua local raw_loadfile = ... -_G._OSVERSION = "OpenOS 1.8.2" +_G._OSVERSION = "OpenOS 1.8.3" -- luacheck: globals component computer unicode _OSVERSION local component = component diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index df2cd8cf8a..150863fd09 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -1405,7 +1405,7 @@ local libcomputer = { if signal.n > 0 then return table.unpack(signal, 1, signal.n) end - until computer.uptime() >= deadline + until (computer.uptime() - deadline) >= -0.001 end, beep = function(...) From 5a685f87c0aada7fbfa8bffb0d5d301acf28e070 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Fri, 7 Jul 2023 17:43:24 +0200 Subject: [PATCH 09/12] Revert "propose fix for #3628" This reverts commit f275e0d44ae9bac643c94f78c3a605b9426fcdea. --- changelog.md | 8 ++------ .../assets/opencomputers/loot/network/data/bin/ping.lua | 6 +++--- .../assets/opencomputers/loot/openos/boot/02_os.lua | 2 +- .../assets/opencomputers/loot/openos/lib/core/boot.lua | 2 +- src/main/resources/assets/opencomputers/lua/machine.lua | 2 +- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index 404a6d10d7..24fd723e0a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,11 +1,7 @@ ## Fixes/improvements -* [#3635] Fix ArrayIndexOutOfBoundsException when using servers with 3 network cards -* [#3634] Fix Internet card selector update logic erroneously dropping non-ready keys - -## OpenOS fixes/improvements - -* [#3628] Fix jitter when using os.sleep() +* [#3635] ArrayIndexOutOfBoundsException when using servers with 3 network cards +* [#3634] Internet card selector update logic erroneously drops non-ready keys ## List of contributors diff --git a/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua b/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua index 2801a1311b..6ba9fc7312 100644 --- a/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua +++ b/src/main/resources/assets/opencomputers/loot/network/data/bin/ping.lua @@ -51,7 +51,7 @@ local function doSleep() local deadline = computer.uptime() + (tonumber(options.i) or tonumber(options.interval) or 1) repeat event.pull(deadline - computer.uptime()) - until (computer.uptime() - deadline) >= -0.001 + until computer.uptime() >= deadline end local function doPing() @@ -66,9 +66,9 @@ local function doPing() local e, replier, id, inpayload repeat e, replier, id, inpayload = event.pull(deadline - computer.uptime(), "ping_reply") - until ((computer.uptime() - deadline) >= -0.001) or (e == "ping_reply" and id == icmp_seq) + until computer.uptime() >= deadline or (e == "ping_reply" and id == icmp_seq) - if ((computer.uptime() - deadline) >= -0.001) and e ~= "ping_reply" then + if computer.uptime() >= deadline and e ~= "ping_reply" then verbose(tostring(len).." bytes lost: icmp_seq="..tostring(icmp_seq)) elseif inpayload == payload then stats.received = stats.received + 1 diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua index d193f94809..314635696a 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua @@ -27,7 +27,7 @@ function os.sleep(timeout) local deadline = computer.uptime() + (timeout or 0) repeat event.pull(deadline - computer.uptime()) - until (computer.uptime() - deadline) >= -0.001 + until computer.uptime() >= deadline end os.setenv("PATH", "/bin:/usr/bin:/home/bin:.") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua index c18cc4c951..2955da2921 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua @@ -1,7 +1,7 @@ -- called from /init.lua local raw_loadfile = ... -_G._OSVERSION = "OpenOS 1.8.3" +_G._OSVERSION = "OpenOS 1.8.2" -- luacheck: globals component computer unicode _OSVERSION local component = component diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index 150863fd09..df2cd8cf8a 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -1405,7 +1405,7 @@ local libcomputer = { if signal.n > 0 then return table.unpack(signal, 1, signal.n) end - until (computer.uptime() - deadline) >= -0.001 + until computer.uptime() >= deadline end, beep = function(...) From d13c015357fd6c42e0a1bdd6e1ef9462f0450a15 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Fri, 7 Jul 2023 17:45:01 +0200 Subject: [PATCH 10/12] Rework Internet Card filtering system. --- changelog.md | 4 + ...omputersConfigCommentManipulationHook.java | 27 ++++ .../java/li/cil/oc/util/InetAddressRange.java | 64 +++++++++ src/main/resources/application.conf | 57 ++++---- src/main/scala/li/cil/oc/OpenComputers.scala | 26 ++++ src/main/scala/li/cil/oc/Settings.scala | 131 ++++++++++++------ .../oc/server/component/InternetCard.scala | 53 ++++++- .../cil/oc/util/InternetFilteringRule.scala | 125 +++++++++++++++++ .../li/cil/oc/util/ThreadPoolFactory.scala | 6 +- .../scala/InternetFilteringRuleTest.scala | 95 +++++++++++++ 10 files changed, 515 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/typesafe/config/impl/OpenComputersConfigCommentManipulationHook.java create mode 100644 src/main/java/li/cil/oc/util/InetAddressRange.java create mode 100644 src/main/scala/li/cil/oc/util/InternetFilteringRule.scala create mode 100644 src/test/scala/InternetFilteringRuleTest.scala diff --git a/changelog.md b/changelog.md index 24fd723e0a..3f8a41a762 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ ## Fixes/improvements +* Reworked Internet Card filtering rules. + * Implemented a new, more powerful system and improved default configuration. + * Internet Card rules are now stored in the "internet.filteringRules" configuration key. + * The old keys ("internet.whitelist", "internet.blacklist") are no longer used; an automatic migration is done upon upgrading the mod. * [#3635] ArrayIndexOutOfBoundsException when using servers with 3 network cards * [#3634] Internet card selector update logic erroneously drops non-ready keys diff --git a/src/main/java/com/typesafe/config/impl/OpenComputersConfigCommentManipulationHook.java b/src/main/java/com/typesafe/config/impl/OpenComputersConfigCommentManipulationHook.java new file mode 100644 index 0000000000..67666a26b0 --- /dev/null +++ b/src/main/java/com/typesafe/config/impl/OpenComputersConfigCommentManipulationHook.java @@ -0,0 +1,27 @@ +package com.typesafe.config.impl; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigValue; +import org.luaj.vm2.ast.Str; + +import java.util.List; + +public final class OpenComputersConfigCommentManipulationHook { + private OpenComputersConfigCommentManipulationHook() { + + } + + public static Config setComments(Config config, String path, List comments) { + return config.withValue(path, setComments(config.getValue(path), comments)); + } + + public static ConfigValue setComments(ConfigValue value, List comments) { + if (value.origin() instanceof SimpleConfigOrigin && value instanceof AbstractConfigValue) { + return ((AbstractConfigValue) value).withOrigin( + ((SimpleConfigOrigin) value.origin()).setComments(comments) + ); + } else { + return value; + } + } +} diff --git a/src/main/java/li/cil/oc/util/InetAddressRange.java b/src/main/java/li/cil/oc/util/InetAddressRange.java new file mode 100644 index 0000000000..3f1898cfe7 --- /dev/null +++ b/src/main/java/li/cil/oc/util/InetAddressRange.java @@ -0,0 +1,64 @@ + +package li.cil.oc.util; + +import com.google.common.net.InetAddresses; + +import java.net.InetAddress; + +// Originally by SquidDev +public final class InetAddressRange { + private final byte[] min; + private final byte[] max; + + InetAddressRange(byte[] min, byte[] max) { + this.min = min; + this.max = max; + } + + public boolean matches(InetAddress address) { + byte[] entry = address.getAddress(); + if (entry.length != min.length) return false; + + for (int i = 0; i < entry.length; i++) { + int value = 0xFF & entry[i]; + if (value < (0xFF & min[i]) || value > (0xFF & max[i])) return false; + } + + return true; + } + + public static InetAddressRange parse(String addressStr, String prefixSizeStr) { + int prefixSize; + try { + prefixSize = Integer.parseInt(prefixSizeStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format("Malformed address range entry '%s': Cannot extract size of CIDR mask from '%s'.", + addressStr + '/' + prefixSizeStr, prefixSizeStr)); + } + + InetAddress address; + try { + address = InetAddresses.forString(addressStr); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(String.format("Malformed address range entry '%s': Cannot extract IP address from '%s'.", + addressStr + '/' + prefixSizeStr, addressStr)); + } + + // Mask the bytes of the IP address. + byte[] minBytes = address.getAddress(), maxBytes = address.getAddress(); + int size = prefixSize; + for (int i = 0; i < minBytes.length; i++) { + if (size <= 0) { + minBytes[i] = (byte) 0; + maxBytes[i] = (byte) 0xFF; + } else if (size < 8) { + minBytes[i] = (byte) (minBytes[i] & 0xFF << (8 - size)); + maxBytes[i] = (byte) (maxBytes[i] | ~(0xFF << (8 - size))); + } + + size -= 8; + } + + return new InetAddressRange(minBytes, maxBytes); + } +} \ No newline at end of file diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index b823836391..b55cd3c491 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -963,37 +963,44 @@ opencomputers { # the `connect` method on internet card components becomes available. enableTcp: true - # This is a list of forbidden domain names. If an HTTP request is made - # or a socket connection is opened the target address will be compared - # to the addresses / address ranges in this list. It it is present in this - # list, the request will be denied. - # Entries are either domain names (www.example.com) or IP addresses in - # string format (10.0.0.3), optionally in CIDR notation to make it easier - # to define address ranges (1.0.0.0/8). Domains are resolved to their - # actual IP once on startup, future requests are resolved and compared - # to the resolved addresses. - # By default all local addresses are blocked. This is only meant as a + # This is a list of filtering rules. For any HTTP request or TCP socket + # connection, the target address will be processed by each rule, starting + # from first to last. The first matching rule will be applied; if no rule + # contains a match, the request will be denied. + # Two types of rules are currently supported: "allow", which allows an + # address to be accessed, and "deny", which forbids such access. + # Rules can be suffixed with additional filters to limit their scope: + # - all: apply to all addresses + # - default: apply built-in allow/deny rules; these may not be up to date, + # so one should primarily rely on them as a fallback + # - private: apply to all private addresses + # - bogon: apply to all known bogon addresses + # - ipv4: apply to all IPv4 addresses + # - ipv6: apply to all IPv6 addresses + # - ipv4-embedded-ipv6: apply to all IPv4 addresses embedded in IPv6 + # addresses + # - ip:[address]: apply to this IP address in string format (10.0.0.3). + # CIDR notation is supported and allows defining address ranges + # (1.0.0.0/8). + # - domain:[domain]: apply to this domain. Domains are resolved to their + # actual IP only once (on startup), future requests are resolved and + # compared to the resolved addresses. Wildcards are not supported. + # The "removeme" rule does not have any use, but is instead present to + # detect whether to emit a warning on dedicated server configurations. + # Modpack authors are asked not to remove this rule; server administrators + # are free to remove it once the filtering rules have been adjusted. + # By default all private addresses are blocked. This is only meant as a # thin layer of security, to avoid average users hosting a game on their # local machine having players access services in their local network. # Server hosters are expected to configure their network outside of the # mod's context in an appropriate manner, e.g. using a system firewall. - blacklist: [ - "127.0.0.0/8" - "0.0.0.0/8" - "10.0.0.0/8" - "192.168.0.0/16" - "172.16.0.0/12" + filteringRules: [ + "removeme", + "deny private", + "deny bogon", + "allow default" ] - # This is a list of allowed domain names. Requests may only be made - # to addresses that are present in this list. If this list is empty, - # requests may be made to all addresses not forbidden. Note that the - # blacklist is always applied, so if an entry is present in both the - # whitelist and the blacklist, the blacklist will win. - # Entries are of the same format as in the blacklist. Examples: - # "gist.github.com", "www.pastebin.com" - whitelist: [] - # The time in seconds to wait for a response to a request before timing # out and returning an error message. If this is zero (the default) the # request will never time out. diff --git a/src/main/scala/li/cil/oc/OpenComputers.scala b/src/main/scala/li/cil/oc/OpenComputers.scala index fa57c1138e..2f30be48f6 100644 --- a/src/main/scala/li/cil/oc/OpenComputers.scala +++ b/src/main/scala/li/cil/oc/OpenComputers.scala @@ -60,6 +60,32 @@ object OpenComputers { def serverStart(e: FMLServerStartingEvent): Unit = { CommandHandler.register(e) ThreadPoolFactory.safePools.foreach(_.newThreadPool()) + + if (Settings.get.internetAccessConfigured()) { + if (Settings.get.internetFilteringRulesInvalid()) { + OpenComputers.log.warn("####################################################") + OpenComputers.log.warn("# #") + OpenComputers.log.warn("# Could not parse Internet Card filtering rules! #") + OpenComputers.log.warn("# Review the server log and adjust the filtering #") + OpenComputers.log.warn("# list to ensure it is appropriately configured. #") + OpenComputers.log.warn("# (config/OpenComputers.cfg => filteringRules) #") + OpenComputers.log.warn("# Internet access has been automatically disabled. #") + OpenComputers.log.warn("# #") + OpenComputers.log.warn("####################################################") + } else if (!Settings.get.internetFilteringRulesObserved && e.getServer.isDedicatedServer) { + OpenComputers.log.warn("####################################################") + OpenComputers.log.warn("# #") + OpenComputers.log.warn("# It appears that you're running a dedicated #") + OpenComputers.log.warn("# server with OpenComputers installed! Make sure #") + OpenComputers.log.warn("# to review the Internet Card address filtering #") + OpenComputers.log.warn("# list to ensure it is appropriately configured. #") + OpenComputers.log.warn("# (config/OpenComputers.cfg => filteringRules) #") + OpenComputers.log.warn("# #") + OpenComputers.log.warn("####################################################") + } else { + OpenComputers.log.info(f"Successfully applied ${Settings.get.internetFilteringRules.length} Internet Card filtering rules.") + } + } } @EventHandler diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 295c22afe4..ee69aa7d55 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -1,28 +1,28 @@ package li.cil.oc -import java.io._ -import java.net.Inet4Address -import java.net.InetAddress -import java.nio.charset.StandardCharsets -import java.security.SecureRandom -import java.util.UUID import com.google.common.net.InetAddresses import com.mojang.authlib.GameProfile import com.typesafe.config._ +import com.typesafe.config.impl.OpenComputersConfigCommentManipulationHook import cpw.mods.fml.common.Loader import cpw.mods.fml.common.versioning.{DefaultArtifactVersion, VersionRange} import li.cil.oc.Settings.DebugCardAccess import li.cil.oc.common.Tier -import li.cil.oc.integration.Mods import li.cil.oc.server.component.DebugCard import li.cil.oc.server.component.DebugCard.AccessContext +import li.cil.oc.util.{InetAddressRange, InternetFilteringRule} import org.apache.commons.codec.binary.Hex import org.apache.commons.lang3.StringEscapeUtils +import java.io._ +import java.net.{Inet4Address, Inet6Address, InetAddress} +import java.nio.charset.StandardCharsets +import java.security.SecureRandom +import java.util.UUID +import scala.collection.JavaConverters._ import scala.collection.convert.WrapAsScala._ import scala.collection.mutable -import scala.io.Codec -import scala.io.Source +import scala.io.{Codec, Source} import scala.util.matching.Regex class Settings(val config: Config) { @@ -299,8 +299,11 @@ class Settings(val config: Config) { val httpEnabled = config.getBoolean("internet.enableHttp") val httpHeadersEnabled = config.getBoolean("internet.enableHttpHeaders") val tcpEnabled = config.getBoolean("internet.enableTcp") - val httpHostBlacklist = Array(config.getStringList("internet.blacklist").map(new Settings.AddressValidator(_)): _*) - val httpHostWhitelist = Array(config.getStringList("internet.whitelist").map(new Settings.AddressValidator(_)): _*) + val internetFilteringRules = Array(config.getStringList("internet.filteringRules") + .filter(p => !p.equals("removeme")) + .map(new InternetFilteringRule(_)): _*) + val internetFilteringRulesObserved = !config.getStringList("internet.filteringRules") + .contains("removeme") val httpTimeout = (config.getInt("internet.requestTimeout") max 0) * 1000 val maxConnections = config.getInt("internet.maxTcpConnections") max 0 val internetThreads = config.getInt("internet.threads") max 1 @@ -484,6 +487,18 @@ class Settings(val config: Config) { val maxNetworkClientPacketDistance: Double = config.getDouble("misc.maxNetworkClientPacketDistance") max 0 val maxNetworkClientEffectPacketDistance: Double = config.getDouble("misc.maxNetworkClientEffectPacketDistance") max 0 val maxNetworkClientSoundPacketDistance: Double = config.getDouble("misc.maxNetworkClientSoundPacketDistance") max 0 + + def internetFilteringRulesInvalid(): Boolean = { + internetFilteringRules.exists(p => p.invalid()) + } + + def internetAccessConfigured(): Boolean = { + httpEnabled || tcpEnabled + } + + def internetAccessAllowed(): Boolean = { + internetAccessConfigured() && !internetFilteringRulesInvalid() + } } object Settings { @@ -496,6 +511,11 @@ object Settings { val deviceComplexityByTier: Array[Int] = Array(12, 24, 32, 9001) var rTreeDebugRenderer = false var blockRenderId: Int = -1 + private val forbiddenConfigLists: List[String] = List( + /* 1.8.3+ filtering rules migration */ + "internet.blacklist", "internet.whitelist" + ) + private val prefix = "opencomputers." def basicScreenPixels: Int = screenResolutionsByTier(0)._1 * screenResolutionsByTier(0)._2 @@ -531,6 +551,13 @@ object Settings { settings = new Settings(defaults.getConfig("opencomputers")) defaults } + for (key <- forbiddenConfigLists) { + if (config.hasPath(prefix + key)) { + if (!config.getStringList(prefix + key).isEmpty) { + throw new RuntimeException("Error parsing configuration file: removed configuration option '" + key + "' is not empty. This option should no longer be used.") + } + } + } try { val renderSettings = ConfigRenderOptions.defaults.setJson(false).setOriginComments(false) val nl = sys.props("line.separator") @@ -581,13 +608,13 @@ object Settings { "computer.robot.limitFlightHeight" ) ) + private val fileringRulesPatchVersion = VersionRange.createFromVersionSpec("[0.0, 1.8.3)") // Checks the config version (i.e. the version of the mod the config was // created by) against the current version to see if some hard changes // were made. If so, the new default values are copied over. private def patchConfig(config: Config, defaults: Config) = { val mod = Loader.instance.activeModContainer - val prefix = "opencomputers." val configVersion = new DefaultArtifactVersion(if (config.hasPath(prefix + "version")) config.getString(prefix + "version") else "0.0.0") var patched = config if (configVersion.compareTo(mod.getProcessedVersion) != 0) { @@ -596,7 +623,7 @@ object Settings { for ((version, paths) <- configPatches if version.containsVersion(configVersion)) { for (path <- paths) { val fullPath = prefix + path - OpenComputers.log.info(s"Updating setting '$fullPath'. ") + OpenComputers.log.info(s"=> Updating setting '$fullPath'. ") if (defaults.hasPath(fullPath)) { patched = patched.withValue(fullPath, defaults.getValue(fullPath)) } @@ -605,35 +632,59 @@ object Settings { } } } - } - patched - } - val cidrPattern = """(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:/(\d{1,2}))""".r - - class AddressValidator(val value: String) { - val validator: (InetAddress, String) => Option[Boolean] = try cidrPattern.findFirstIn(value) match { - case Some(cidrPattern(address, prefix)) => - val addr = InetAddresses.coerceToInteger(InetAddresses.forString(address)) - val mask = 0xFFFFFFFF << (32 - prefix.toInt) - val min = addr & mask - val max = min | ~mask - (inetAddress: InetAddress, host: String) => Some(inetAddress match { - case v4: Inet4Address => - val numeric = InetAddresses.coerceToInteger(v4) - min <= numeric && numeric <= max - case _ => true // Can't check IPv6 addresses so we pass them. - }) - case _ => - val address = InetAddress.getByName(value) - (inetAddress: InetAddress, host: String) => Some(host == value || inetAddress == address) - } catch { - case t: Throwable => - OpenComputers.log.warn("Invalid entry in internet blacklist / whitelist: " + value, t) - (inetAddress: InetAddress, host: String) => None + // Migrate filtering rules to 1.8.3+ + if (fileringRulesPatchVersion.containsVersion(configVersion)) { + OpenComputers.log.info(s"=> Migrating Internet Card filtering rules. ") + val cidrPattern = """(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:/(\d{1,2}))""".r + val httpHostWhitelist = patched.getStringList(prefix + "internet.whitelist") + val httpHostBlacklist = patched.getStringList(prefix + "internet.blacklist") + val internetFilteringRules = mutable.MutableList[String]() + for (blockedAddress <- httpHostBlacklist) { + if (cidrPattern.findFirstIn(blockedAddress).isDefined) { + internetFilteringRules += "deny ip:" + blockedAddress + } else { + internetFilteringRules += "deny domain:" + blockedAddress + } + } + for (allowedAddress <- httpHostWhitelist) { + if (cidrPattern.findFirstIn(allowedAddress).isDefined) { + internetFilteringRules += "allow ip:" + allowedAddress + } else { + internetFilteringRules += "allow domain:" + allowedAddress + } + } + if (!httpHostWhitelist.isEmpty) { + internetFilteringRules += "deny all" + } + for (defaultRule <- defaults.getStringList(prefix + "internet.filteringRules")) { + internetFilteringRules += defaultRule + } + var patchedRules: ConfigValue = ConfigValueFactory.fromIterable(internetFilteringRules.asJava) + // We need to use the private APIs here, unfortunately. + try { + for (key <- List("internet.whitelist", "internet.blacklist")) { + if (patched.hasPath(prefix + key)) { + val originalValue = patched.getValue(prefix + key) + var deprecatedValue: ConfigValue = ConfigValueFactory.fromIterable(new java.util.ArrayList[String](), originalValue.origin().description()) + val comments = mutable.MutableList("No longer used! See internet.filteringRules.", "", "Previous contents:") + for (value <- patched.getStringList(prefix + key)) { + comments += "\"" + value + "\"" + } + deprecatedValue = OpenComputersConfigCommentManipulationHook.setComments(deprecatedValue, comments.asJava) + patched = patched.withValue(prefix + key, deprecatedValue) + } + } + patchedRules = OpenComputersConfigCommentManipulationHook.setComments( + patchedRules, defaults.getValue(prefix + "internet.filteringRules").origin().comments() + ) + } catch { + case _: Throwable => /* pass */ + } + patched = patched.withValue(prefix + "internet.filteringRules", patchedRules) + } } - - def apply(inetAddress: InetAddress, host: String) = validator(inetAddress, host) + patched } sealed trait DebugCardAccess { diff --git a/src/main/scala/li/cil/oc/server/component/InternetCard.scala b/src/main/scala/li/cil/oc/server/component/InternetCard.scala index 2b15bf2ade..2c6268269a 100644 --- a/src/main/scala/li/cil/oc/server/component/InternetCard.scala +++ b/src/main/scala/li/cil/oc/server/component/InternetCard.scala @@ -1,5 +1,7 @@ package li.cil.oc.server.component +import com.google.common.net.InetAddresses + import java.io.BufferedWriter import java.io.FileNotFoundException import java.io.IOException @@ -13,7 +15,6 @@ import java.nio.channels.SocketChannel import java.util import java.util.UUID import java.util.concurrent._ - import li.cil.oc.Constants import li.cil.oc.OpenComputers import li.cil.oc.Settings @@ -63,6 +64,9 @@ class InternetCard extends prefab.ManagedEnvironment with DeviceInfo { def request(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { checkOwner(context) val address = args.checkString(0) + if (!Settings.get.internetAccessAllowed()) { + return result(Unit, "internet access is unavailable") + } if (!Settings.get.httpEnabled) { return result(Unit, "http requests are unavailable") } @@ -91,6 +95,9 @@ class InternetCard extends prefab.ManagedEnvironment with DeviceInfo { checkOwner(context) val address = args.checkString(0) val port = args.optInteger(1, -1) + if (!Settings.get.internetAccessAllowed()) { + return result(Unit, "internet access is unavailable") + } if (!Settings.get.tcpEnabled) { return result(Unit, "tcp connections are unavailable") } @@ -179,7 +186,11 @@ class InternetCard extends prefab.ManagedEnvironment with DeviceInfo { } object InternetCard { - private val threadPool = ThreadPoolFactory.create("Internet", Settings.get.internetThreads) + // For InternetFilteringRuleTest, where Settings.get is not provided. + private val threadPool = ThreadPoolFactory.create("Internet", Option(Settings.get) match { + case None => 1 + case Some(settings) => settings.internetThreads + }) trait Closable { def close(): Unit @@ -355,12 +366,40 @@ object InternetCard { } - def checkLists(inetAddress: InetAddress, host: String) { - if (Settings.get.httpHostWhitelist.length > 0 && !Settings.get.httpHostWhitelist.exists(i => i.apply(inetAddress, host).getOrElse(false))) { - throw new FileNotFoundException("address is not whitelisted") + def isRequestAllowed(settings: Settings, inetAddress: InetAddress, host: String): Boolean = { + if (!settings.internetAccessAllowed()) { + false + } else { + val rules = settings.internetFilteringRules + inetAddress match { + // IPv6 handling + case inet6Address: Inet6Address => + // If the IP address is an IPv6 address with an embedded IPv4 address, and the IPv4 address is blocked, + // block this request. + if (InetAddresses.hasEmbeddedIPv4ClientAddress(inet6Address)) { + val inet4in6Address = InetAddresses.getEmbeddedIPv4ClientAddress(inet6Address) + if (!rules.map(r => r.apply(inet4in6Address, host)).collectFirst({ case Some(r) => r }).getOrElse(true)) { + return false + } + } + + // Process address as an IPv6 address. + rules.map(r => r.apply(inet6Address, host)).collectFirst({ case Some(r) => r }).getOrElse(false) + // IPv4 handling + case inet4Address: Inet4Address => + // Process address as an IPv4 address. + rules.map(r => r.apply(inet4Address, host)).collectFirst({ case Some(r) => r }).getOrElse(false) + case _ => + // Unrecognized address type - block. + OpenComputers.log.warn("Internet Card blocked unrecognized address type: " + inetAddress.toString) + false + } } - if (Settings.get.httpHostBlacklist.length > 0 && Settings.get.httpHostBlacklist.exists(i => i.apply(inetAddress, host).getOrElse(true))) { - throw new FileNotFoundException("address is blacklisted") + } + + def checkLists(inetAddress: InetAddress, host: String): Unit = { + if (!isRequestAllowed(Settings.get, inetAddress, host)) { + throw new FileNotFoundException("address is not allowed") } } diff --git a/src/main/scala/li/cil/oc/util/InternetFilteringRule.scala b/src/main/scala/li/cil/oc/util/InternetFilteringRule.scala new file mode 100644 index 0000000000..30837d7f7b --- /dev/null +++ b/src/main/scala/li/cil/oc/util/InternetFilteringRule.scala @@ -0,0 +1,125 @@ +package li.cil.oc.util + +import com.google.common.net.InetAddresses +import li.cil.oc.OpenComputers + +import java.net.{Inet4Address, Inet6Address, InetAddress} +import scala.collection.mutable + +class InternetFilteringRule(val ruleString: String) { + private var _invalid: Boolean = false + private val validator: (InetAddress, String) => Option[Boolean] = { + try { + val ruleParts = ruleString.split(' ') + ruleParts.head match { + case "allow" | "deny" => + val value = ruleParts.head.equals("allow") + val predicates = mutable.MutableList[(InetAddress, String) => Boolean]() + ruleParts.tail.foreach(f => { + val filter = f.split(":", 2) + filter.head match { + case "default" => + if (!value) { + predicates += ((_: InetAddress, _: String) => { false }) + } else { + predicates += ((inetAddress: InetAddress, host: String) => { + InternetFilteringRule.defaultRules.map(r => r.apply(inetAddress, host)).collectFirst({ case Some(r) => r }).getOrElse(false) + }) + } + case "private" => + predicates += ((inetAddress: InetAddress, _: String) => { + inetAddress.isAnyLocalAddress || inetAddress.isLoopbackAddress || inetAddress.isLinkLocalAddress || inetAddress.isSiteLocalAddress + }) + case "bogon" => + predicates += ((inetAddress: InetAddress, _: String) => { + InternetFilteringRule.bogonMatchingRules.exists(rule => rule.matches(inetAddress)) + }) + case "ipv4" => + predicates += ((inetAddress: InetAddress, _: String) => { + inetAddress.isInstanceOf[Inet4Address] + }) + case "ipv6" => + predicates += ((inetAddress: InetAddress, _: String) => { + inetAddress.isInstanceOf[Inet6Address] + }) + case "ipv4-embedded-ipv6" => + predicates += ((inetAddress: InetAddress, _: String) => { + inetAddress.isInstanceOf[Inet6Address] && InetAddresses.hasEmbeddedIPv4ClientAddress(inetAddress.asInstanceOf[Inet6Address]) + }) + case "domain" => + val domain = filter(1) + val addresses = InetAddress.getAllByName(domain) + predicates += ((inetAddress: InetAddress, host: String) => { + host == domain || addresses.exists(a => a.equals(inetAddress)) + }) + case "ip" => + val ipStringParts = filter(1).split("/", 2) + if (ipStringParts.length == 2) { + val ipRange = InetAddressRange.parse(ipStringParts(0), ipStringParts(1)) + predicates += ((inetAddress: InetAddress, _: String) => ipRange.matches(inetAddress)) + } else { + val ipAddress = InetAddresses.forString(ipStringParts(0)) + predicates += ((inetAddress: InetAddress, _: String) => ipAddress.equals(inetAddress)) + } + predicates += ((inetAddress: InetAddress, _: String) => { + inetAddress.isAnyLocalAddress || inetAddress.isLoopbackAddress || inetAddress.isLinkLocalAddress || inetAddress.isSiteLocalAddress + }) + case "all" => + } + }) + (inetAddress: InetAddress, host: String) => { + if (predicates.forall(p => p(inetAddress, host))) + Some(value) + else + None + } + case "removeme" => + // Ignore this rule. + (_: InetAddress, _: String) => None + } + } catch { + case t: Throwable => + OpenComputers.log.error("Invalid Internet filteringRules rule in configuration: \"" + ruleString + "\".", t) + _invalid = true + (_: InetAddress, _: String) => Some(false) + } + } + + def invalid(): Boolean = _invalid + + def apply(inetAddress: InetAddress, host: String) = validator(inetAddress, host) +} + +object InternetFilteringRule { + private val defaultRules = Array( + new InternetFilteringRule("deny private"), + new InternetFilteringRule("deny bogon"), + new InternetFilteringRule("allow all") + ) + private val bogonMatchingRules = Array( + "0.0.0.0/8", + "10.0.0.0/8", + "100.64.0.0/10", + "127.0.0.0/8", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.0.2.0/24", + "192.168.0.0/16", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "224.0.0.0/3", + "::/128", + "::1/128", + "::ffff:0:0/96", + "::/96", + "100::/64", + "2001:10::/28", + "2001:db8::/32", + "fc00::/7", + "fe80::/10", + "fec0::/10", + "ff00::/8" + ).map(s => s.split("/", 2)).map(s => InetAddressRange.parse(s(0), s(1))) +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/util/ThreadPoolFactory.scala b/src/main/scala/li/cil/oc/util/ThreadPoolFactory.scala index 9c22b02b1c..5c78c745ad 100644 --- a/src/main/scala/li/cil/oc/util/ThreadPoolFactory.scala +++ b/src/main/scala/li/cil/oc/util/ThreadPoolFactory.scala @@ -14,7 +14,11 @@ import scala.collection.mutable object ThreadPoolFactory { val priority = { - val custom = Settings.get.threadPriority + // For InternetFilteringRuleTest, where Settings.get is not provided. + val custom = Option(Settings.get) match { + case None => -1 + case Some(settings) => settings.threadPriority + } if (custom < 1) Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 else custom max Thread.MIN_PRIORITY min Thread.MAX_PRIORITY } diff --git a/src/test/scala/InternetFilteringRuleTest.scala b/src/test/scala/InternetFilteringRuleTest.scala new file mode 100644 index 0000000000..9471689311 --- /dev/null +++ b/src/test/scala/InternetFilteringRuleTest.scala @@ -0,0 +1,95 @@ +import com.typesafe.config.ConfigFactory +import li.cil.oc.Settings +import li.cil.oc.server.component.InternetCard +import org.junit.runner.RunWith +import org.scalatest.{FlatSpec, FunSpec, WordSpec} +import org.scalatest.Matchers.{be, convertToAnyShouldWrapper} +import org.scalatest.junit.JUnitRunner +import org.scalatest.mock.MockitoSugar + +import java.net.InetAddress +import scala.compat.Platform.EOL +import scala.io.{Codec, Source} + +@RunWith(classOf[JUnitRunner]) +class InternetFilteringRuleTest extends FunSpec with MockitoSugar { + val config = autoClose(classOf[Settings].getResourceAsStream("/application.conf")) { in => + val configStr = Source.fromInputStream(in)(Codec.UTF8).getLines().mkString("", EOL, EOL) + ConfigFactory.parseString(configStr) + } + val settings = new Settings(config.getConfig("opencomputers")) + + + describe("The default AddressValidators") { + // Many of these payloads are pulled from PayloadsAllTheThings + // https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Request%20Forgery/README.md + it("should accept a valid external address") { + isUriBlacklisted("https://google.com") should be(false) + } + it("should reject localhost") { + isUriBlacklisted("http://localhost") should be(true) + } + it("should reject the local host in IPv4 format") { + isUriBlacklisted("http://127.0.0.1") should be(true) + isUriBlacklisted("http://127.0.1") should be(true) + isUriBlacklisted("http://127.1") should be(true) + isUriBlacklisted("http://0") should be (true) + } + it("should reject the local host in IPv6") { + isUriBlacklisted("http://[::1]") should be(true) + isUriBlacklisted("http://[::]") should be(true) + } + it("should reject IPv6/IPv4 Address Embedding") { + isUriBlacklisted("http://[0:0:0:0:0:ffff:127.0.0.1]") should be(true) + isUriBlacklisted("http://[::ffff:127.0.0.1]") should be(true) + } + it("should reject an attempt to bypass using a decimal IP location") { + isUriBlacklisted("http://2130706433") should be(true) // 127.0.0.1 + isUriBlacklisted("http://3232235521") should be(true) // 192.168.0.1 + isUriBlacklisted("http://3232235777") should be(true) // 192.168.1.1 + } + it("should reject the IMDS address in IPv4 format") { + isUriBlacklisted("http://169.254.169.254") should be(true) + isUriBlacklisted("http://2852039166") should be(true) // 169.254.169.254 + } + it("should reject the IMDS address in IPv6 format") { + isUriBlacklisted("http://[fd00:ec2::254]") should be(true) + } + it("should reject the IMDS in for Oracle Cloud") { + isUriBlacklisted("http://192.0.0.192") should be(true) + } + it("should reject the IMDS in for Alibaba Cloud") { + isUriBlacklisted("http://100.100.100.200") should be(true) + } + } + + def isUriBlacklisted(uri: String): Boolean = { + val uriObj = new java.net.URI(uri) + val resolved = InetAddress.getByName(uriObj.getHost) + !InternetCard.isRequestAllowed(settings, resolved, uriObj.getHost) + } + + def autoClose[A <: AutoCloseable, B](closeable: A)(fun: (A) ⇒ B): B = { + var t: Throwable = null + try { + fun(closeable) + } catch { + case funT: Throwable ⇒ + t = funT + throw t + } finally { + if (t != null) { + try { + closeable.close() + } catch { + case closeT: Throwable ⇒ + t.addSuppressed(closeT) + throw t + } + } else { + closeable.close() + } + } + } + +} From 089dd28d05ab703bf5332284a9fc17722a70ae66 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Fri, 7 Jul 2023 17:45:31 +0200 Subject: [PATCH 11/12] Bump mod version. --- build.properties | 2 +- .../assets/opencomputers/loot/openos/lib/core/boot.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.properties b/build.properties index 65729b05c9..0787ea4c98 100644 --- a/build.properties +++ b/build.properties @@ -1,7 +1,7 @@ minecraft.version=1.7.10 forge.version=10.13.4.1614-1.7.10 -oc.version=1.8.0-snapshot +oc.version=1.8.3-snapshot ae2.version=rv2-stable-10 bc.version=7.1.24 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua index 2955da2921..c18cc4c951 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua @@ -1,7 +1,7 @@ -- called from /init.lua local raw_loadfile = ... -_G._OSVERSION = "OpenOS 1.8.2" +_G._OSVERSION = "OpenOS 1.8.3" -- luacheck: globals component computer unicode _OSVERSION local component = component From cc2b540f1693545841dd6b3d0e010bee35bf0d77 Mon Sep 17 00:00:00 2001 From: KosmosPrime <5663514+KosmosPrime@users.noreply.github.com> Date: Sat, 8 Jul 2023 00:57:44 +0200 Subject: [PATCH 12/12] Update test dependencies to scala 2.13.4 --- build.gradle | 12 ++++++++---- src/test/scala/InternetFilteringRuleTest.scala | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 3ef9706c57..139f2be919 100644 --- a/build.gradle +++ b/build.gradle @@ -193,6 +193,7 @@ dependencies { def jeiSlug = "jei-${config.minecraft.version}" minecraft "net.minecraftforge:forge:${config.minecraft.version}-${config.forge.version}" + // required for tests but cannot use implementation as that would clash with scorge at runtime compileOnly "org.scala-lang:scala-library:2.13.4" implementation "net.minecraftforge:Scorge:${config.scorge.version}" embedded "com.typesafe:config:1.2.1" @@ -238,10 +239,13 @@ dependencies { embedded name: 'OC-JNLua', version: '20230530.0', ext: 'jar' embedded name: 'OC-JNLua-Natives', version: '20220928.1', ext: 'jar' - testImplementation("junit:junit:4.13") - testImplementation("org.mockito:mockito-all:1.10.19") - testImplementation("org.scalactic:scalactic_2.11:2.2.6") - testImplementation("org.scalatest:scalatest_2.11:2.2.6") + testImplementation "org.scala-lang:scala-library:2.13.4" + testImplementation "junit:junit:4.13" + testImplementation "org.mockito:mockito-core:3.4.0" + testImplementation "org.scalactic:scalactic_2.13:3.2.6" + testImplementation "org.scalatest:scalatest_2.13:3.2.6" + testImplementation "org.scalatestplus:junit-4-13_2.13:3.2.6.+" + testImplementation "org.scalatestplus:mockito-3-4_2.13:3.2.6.+" provided fg.deobf("codechicken:EnderStorage:${config.enderstorage.version}:universal") } diff --git a/src/test/scala/InternetFilteringRuleTest.scala b/src/test/scala/InternetFilteringRuleTest.scala index 9471689311..6db6c3067a 100644 --- a/src/test/scala/InternetFilteringRuleTest.scala +++ b/src/test/scala/InternetFilteringRuleTest.scala @@ -2,17 +2,17 @@ import com.typesafe.config.ConfigFactory import li.cil.oc.Settings import li.cil.oc.server.component.InternetCard import org.junit.runner.RunWith -import org.scalatest.{FlatSpec, FunSpec, WordSpec} -import org.scalatest.Matchers.{be, convertToAnyShouldWrapper} -import org.scalatest.junit.JUnitRunner -import org.scalatest.mock.MockitoSugar +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers.{be, convertToAnyShouldWrapper} +import org.scalatestplus.junit.JUnitRunner +import org.scalatestplus.mockito.MockitoSugar import java.net.InetAddress import scala.compat.Platform.EOL import scala.io.{Codec, Source} @RunWith(classOf[JUnitRunner]) -class InternetFilteringRuleTest extends FunSpec with MockitoSugar { +class InternetFilteringRuleTest extends AnyFunSpec with MockitoSugar { val config = autoClose(classOf[Settings].getResourceAsStream("/application.conf")) { in => val configStr = Source.fromInputStream(in)(Codec.UTF8).getLines().mkString("", EOL, EOL) ConfigFactory.parseString(configStr) @@ -33,7 +33,7 @@ class InternetFilteringRuleTest extends FunSpec with MockitoSugar { isUriBlacklisted("http://127.0.0.1") should be(true) isUriBlacklisted("http://127.0.1") should be(true) isUriBlacklisted("http://127.1") should be(true) - isUriBlacklisted("http://0") should be (true) + isUriBlacklisted("http://0") should be(true) } it("should reject the local host in IPv6") { isUriBlacklisted("http://[::1]") should be(true)