Merge pull request #104 from asiekierka/gtnh-oc-1.8.3

Update to OpenComputers 1.8.3
This commit is contained in:
Martin Robertz 2023-07-07 21:30:48 +02:00 committed by GitHub
commit fc584f34a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 534 additions and 111 deletions

View File

@ -1,30 +1,12 @@
## 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.
* 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
## List of contributors
asie, ds84182, Possseidon, repo-alt, sanmofe
asie, Fingercomp

View File

@ -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<String> comments) {
return config.withValue(path, setComments(config.getValue(path), comments));
}
public static ConfigValue setComments(ConfigValue value, List<String> comments) {
if (value.origin() instanceof SimpleConfigOrigin && value instanceof AbstractConfigValue) {
return ((AbstractConfigValue) value).withOrigin(
((SimpleConfigOrigin) value.origin()).setComments(comments)
);
} else {
return value;
}
}
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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) {
@ -300,8 +300,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
@ -485,6 +488,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 {
@ -497,6 +512,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
@ -532,6 +552,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")
@ -582,13 +609,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) {
@ -597,7 +624,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))
}
@ -606,37 +633,61 @@ object Settings {
}
}
}
// 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)
}
}
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
}
def apply(inetAddress: InetAddress, host: String) = validator(inetAddress, host)
}
sealed trait DebugCardAccess {
def checkAccess(ctx: Option[DebugCard.AccessContext]): Option[String]
}

View File

@ -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) {

View File

@ -264,7 +264,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.

View File

@ -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
@ -209,7 +220,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()
@ -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
}
if (Settings.get.httpHostBlacklist.length > 0 && Settings.get.httpHostBlacklist.exists(i => i.apply(inetAddress, host).getOrElse(true))) {
throw new FileNotFoundException("address is blacklisted")
}
// 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
}
}
}
def checkLists(inetAddress: InetAddress, host: String): Unit = {
if (!isRequestAllowed(Settings.get, inetAddress, host)) {
throw new FileNotFoundException("address is not allowed")
}
}

View File

@ -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)))
}

View File

@ -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
}

View File

@ -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()
}
}
}
}

View File

@ -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