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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/network.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1737,11 +1737,16 @@ pub const Connection = struct { // MARK: Connection

pub fn receive(self: *Connection, data: []const u8) void {
self.tryReceive(data) catch |err| {
std.log.err("Got error while processing received network data: {s}", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
std.log.info("{f}", .{main.fmt.FormatErrorTrace{.stackTrace = trace.*}});
switch (err) {
error.NotWhitelisted => {},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this code here should worry about the error sets of arbitrary network protocols. I assume you want to get rid of the error popup? I think the right solution here would be to change the error to a warning, there are many other cases where this code is run (e.g. version differences) that probably shouldn't have the error popup either.

else => {
std.log.err("Got error while processing received network data: {s}", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
std.log.info("{f}", .{main.fmt.FormatErrorTrace{.stackTrace = trace.*}});
}
std.log.debug("Packet data: {any}", .{data});
},
}
std.log.debug("Packet data: {any}", .{data});
self.disconnect();
};
}
Expand Down
12 changes: 12 additions & 0 deletions src/network/protocols.zig
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ pub const handShake = struct { // MARK: handShake
const keys = zon.getChild("keys");
try conn.user.?.identifyFromKeysAndName(name, keys);

switch (main.server.players.isAllowedToJoin(conn.user.?.newKeyString.?)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to have this switch and the check for whitelistEnabled inside the isAllowedToJoin function, also in my opinion the same generic message for both paths would be enough.

.allowed => {},
.blocked => {
std.log.info("Rejected connection from '{s}': blocked", .{name});
Comment thread
IntegratedQuantum marked this conversation as resolved.
Outdated
return error.NotWhitelisted;
},
.neutral => if (main.server.world.?.settings.whitelistEnabled) {
std.log.info("Rejected connection from '{s}': not on whitelist", .{name});
return error.NotWhitelisted;
},
}

var writer: utils.BinaryWriter = .init(main.stackAllocator);
defer writer.deinit();
writer.writeEnum(Connection.HandShakeState, .signatureRequest);
Expand Down
16 changes: 16 additions & 0 deletions src/server/command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,22 @@ pub const PlayerIndex = struct {
}
};

pub const KeyString = struct {
key: []const u8,

pub fn parse(_: NeverFailingAllocator, name: []const u8, arg: []const u8, errorMessage: *ListManaged(u8)) error{ParseError}!KeyString {
const colonIndex = std.mem.indexOfScalar(u8, arg, ':') orelse {
errorMessage.print("Expected a public key of the form \"<keyType>:<base64>\" for <{s}>, found \"{s}\"", .{name, arg});
Comment thread
IntegratedQuantum marked this conversation as resolved.
return error.ParseError;
};
_ = std.meta.stringToEnum(main.network.authentication.KeyTypeEnum, arg[0..colonIndex]) orelse {
errorMessage.print("Unknown key type \"{s}\" for <{s}>", .{arg[0..colonIndex], name});
return error.ParseError;
};
return .{.key = arg};
}
};

pub const BiomeId = struct {
biome: *const main.server.terrain.biomes.Biome,

Expand Down
1 change: 1 addition & 0 deletions src/server/command/_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub const spawn = @import("spawn.zig");
pub const tickspeed = @import("tickspeed.zig");
pub const time = @import("time.zig");
pub const tp = @import("tp.zig");
pub const whitelist = @import("whitelist.zig");

pub const avatar = @import("entity/avatar.zig");

Expand Down
51 changes: 51 additions & 0 deletions src/server/command/whitelist.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const std = @import("std");

const main = @import("main");
const command = main.server.command;
const Source = command.Source;
const players = main.server.players;

pub const description = "Manages the connection whitelist";
pub const usage =
\\/whitelist <add/block> <keyType>:<base64Key>
\\/whitelist <add/block> @<playerIndex>
Comment thread
IntegratedQuantum marked this conversation as resolved.
;

const Action = enum { add, block };

pub const Args = union(enum) {
@"/whitelist <action> <key>": struct { action: Action, key: command.KeyString },
@"/whitelist <action> <playerIndex>": struct { action: Action, playerIndex: command.PlayerIndex },
};

pub fn execute(args: Args, source: Source) void {
switch (args) {
.@"/whitelist <action> <key>" => |params| applyAction(source, params.action, params.key.key),
.@"/whitelist <action> <playerIndex>" => |params| {
const target = command.Target.fromPlayerIndex(params.playerIndex, source) catch return;
const key = target.user.newKeyString orelse {
source.sendMessage("#ff0000Player {s}§#ff0000 has no public key to whitelist", .{target.user.name});
return;
};
applyAction(source, params.action, key);
},
}
}

fn applyAction(source: Source, action: Action, key: []const u8) void {
switch (action) {
.add => switch (players.add(key)) {
.added => source.sendMessage("#00ff00Added {s}§#00ff00 to the whitelist", .{key}),
.alreadyAllowed => source.sendMessage("#ff0000{s}§#ff0000 is already on the whitelist", .{key}),
},
.block => {
switch (players.block(key)) {
.blocked => source.sendMessage("#00ff00Blocked {s}§#00ff00 from connecting", .{key}),
.alreadyBlocked => source.sendMessage("#ff0000{s}§#ff0000 is already blocked", .{key}),
}
if (main.server.getUserByKey(key)) |user| {
user.conn.disconnect();
}
},
}
}
31 changes: 17 additions & 14 deletions src/server/players.zig
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ fn saveBlocked(index: usize, value: bool) void {
const AddResult = enum { added, alreadyAllowed };

pub fn add(key: []const u8) AddResult {
sync.threadContext.assertCorrectContext(.server);
mutex.lock();
defer mutex.unlock();
const result = ensurePlayerRecord(key);
Expand All @@ -176,6 +177,7 @@ pub fn add(key: []const u8) AddResult {
const BlockResult = enum { blocked, alreadyBlocked };

pub fn block(key: []const u8) BlockResult {
sync.threadContext.assertCorrectContext(.server);
mutex.lock();
defer mutex.unlock();
const result = ensurePlayerRecord(key);
Expand All @@ -185,12 +187,13 @@ pub fn block(key: []const u8) BlockResult {
return if (result.wasNew or !wasBlocked) .blocked else .alreadyBlocked;
}

pub fn isAllowedToJoin(key: []const u8) bool {
sync.threadContext.assertCorrectContext(.server);
const JoinResult = enum { allowed, neutral, blocked };

pub fn isAllowedToJoin(key: []const u8) JoinResult {
mutex.lock();
defer mutex.unlock();
const entry = playerDatabase.get(key) orelse return false;
return !entry.blocked;
const entry = playerDatabase.get(key) orelse return .neutral;
return if (entry.blocked) .blocked else .allowed;
}

test "addContainsRemove" {
Expand All @@ -199,13 +202,13 @@ test "addContainsRemove" {

init("test", 0);

try std.testing.expectEqual(false, isAllowedToJoin("ed25519:abc"));
try std.testing.expectEqual(.neutral, isAllowedToJoin("ed25519:abc"));
try std.testing.expectEqual(.added, add("ed25519:abc"));
try std.testing.expectEqual(.alreadyAllowed, add("ed25519:abc"));
try std.testing.expectEqual(true, isAllowedToJoin("ed25519:abc"));
try std.testing.expectEqual(.allowed, isAllowedToJoin("ed25519:abc"));
try std.testing.expectEqual(.blocked, block("ed25519:abc"));
try std.testing.expectEqual(.alreadyBlocked, block("ed25519:abc"));
try std.testing.expectEqual(false, isAllowedToJoin("ed25519:abc"));
try std.testing.expectEqual(.blocked, isAllowedToJoin("ed25519:abc"));
}

test "addUnblocks" {
Expand All @@ -215,25 +218,25 @@ test "addUnblocks" {
init("test", 0);

try std.testing.expectEqual(.blocked, block("ed25519:xyz"));
try std.testing.expectEqual(false, isAllowedToJoin("ed25519:xyz"));
try std.testing.expectEqual(.blocked, isAllowedToJoin("ed25519:xyz"));
try std.testing.expectEqual(.added, add("ed25519:xyz"));
try std.testing.expectEqual(true, isAllowedToJoin("ed25519:xyz"));
try std.testing.expectEqual(.allowed, isAllowedToJoin("ed25519:xyz"));
}

test "knownPlayerAllowedByDefaultButBlockable" {
test "allowedNeutralAndBlockedStates" {
main.heap.allocators.createWorldArena();
defer main.heap.allocators.destroyWorldArena();

init("test", 0);

playerDatabase.put(main.worldArena.allocator, main.worldArena.dupe(u8, "ed25519:known"), .{.playerIndex = 0, .blocked = false}) catch unreachable;

try std.testing.expectEqual(true, isAllowedToJoin("ed25519:known"));
try std.testing.expectEqual(false, isAllowedToJoin("ed25519:unknown"));
try std.testing.expectEqual(.allowed, isAllowedToJoin("ed25519:known"));
try std.testing.expectEqual(.neutral, isAllowedToJoin("ed25519:unknown"));

try std.testing.expectEqual(.blocked, block("ed25519:known"));
try std.testing.expectEqual(false, isAllowedToJoin("ed25519:known"));
try std.testing.expectEqual(.blocked, isAllowedToJoin("ed25519:known"));

try std.testing.expectEqual(.added, add("ed25519:known"));
try std.testing.expectEqual(true, isAllowedToJoin("ed25519:known"));
try std.testing.expectEqual(.allowed, isAllowedToJoin("ed25519:known"));
}
11 changes: 11 additions & 0 deletions src/server/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -891,3 +891,14 @@ pub fn getUserByIndex(index: PlayerIndex) ?*User {
}
return null;
}

pub fn getUserByKey(key: []const u8) ?*User {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a one-time use case, there is no need to pollute the namespace with it, everything used inside here is already public, so please inline it to the implementation site.

const userList = getUserList(main.stackAllocator);
defer main.stackAllocator.free(userList);
for (userList) |user| {
if (user.newKeyString) |userKey| {
if (std.mem.eql(u8, userKey, key)) return user;
}
}
return null;
}
3 changes: 3 additions & 0 deletions src/server/world.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub const Settings = struct {
defaultGamemode: Gamemode = .creative,
allowCheats: bool = true,
testingMode: bool = false,
whitelistEnabled: bool = false,
seed: u64 = undefined,

pub const defaults: Settings = .{};
Expand All @@ -48,6 +49,7 @@ pub const Settings = struct {
.defaultGamemode = std.meta.stringToEnum(main.game.Gamemode, zon.get([]const u8, "defaultGamemode") orelse @tagName(defaults.defaultGamemode)) orelse defaults.defaultGamemode,
.allowCheats = zon.get(bool, "allowCheats") orelse defaults.allowCheats,
.testingMode = zon.get(bool, "testingMode") orelse defaults.testingMode,
.whitelistEnabled = zon.get(bool, "whitelistEnabled") orelse defaults.whitelistEnabled,
};
}

Expand All @@ -57,6 +59,7 @@ pub const Settings = struct {
zon.put("defaultGamemode", @tagName(self.defaultGamemode));
zon.put("allowCheats", self.allowCheats);
zon.put("testingMode", self.testingMode);
zon.put("whitelistEnabled", self.whitelistEnabled);
zon.put("seed", self.seed);

return zon;
Expand Down
Loading