diff --git a/source/ircd/channel.d b/source/ircd/channel.d index 9f94261..f9850db 100644 --- a/source/ircd/channel.d +++ b/source/ircd/channel.d @@ -6,6 +6,7 @@ import std.string; import ircd.connection; import ircd.server; import ircd.message; +import ircd.helpers; class Channel { @@ -15,6 +16,7 @@ class Channel Connection[] members; char[] modes; char[][Connection] memberModes; + string[][char] maskLists; string key; //TODO: Fully implement key //TODO: Implement member limit (+l) @@ -24,14 +26,13 @@ class Channel this(string name, Server server) { this.name = name; - this.members = []; this._server = server; + this.maskLists = ['b' : [], 'e' : [], 'I' : []]; } void join(Connection connection) { members ~= connection; - memberModes[connection] = null; if(members.length == 1) { @@ -198,6 +199,60 @@ class Channel return true; } + bool addMaskListEntry(Connection origin, string mask, char mode) + { + if(maskLists[mode].canFind!(m => m.toIRCLower == mask.toIRCLower)) + { + return false; + } + + maskLists[mode] ~= mask; + + return true; + } + + bool removeMaskListEntry(Connection origin, string mask, char mode) + { + if(!maskLists[mode].canFind!(m => m.toIRCLower == mask.toIRCLower)) + { + return false; + } + + maskLists[mode] = maskLists[mode].remove!(m => m.toIRCLower == mask.toIRCLower); + + return true; + } + + void sendBanList(Connection connection) + { + foreach(entry; maskLists['b']) + { + connection.send(Message(_server.name, "367", [connection.nick, name, entry], false)); + } + + connection.send(Message(_server.name, "368", [connection.nick, name, "End of channel ban list"], true)); + } + + void sendExceptList(Connection connection) + { + foreach(entry; maskLists['e']) + { + connection.send(Message(_server.name, "348", [connection.nick, name, entry], false)); + } + + connection.send(Message(_server.name, "349", [connection.nick, name, "End of channel exception list"], true)); + } + + void sendInviteList(Connection connection) + { + foreach(entry; maskLists['I']) + { + connection.send(Message(_server.name, "346", [connection.nick, name, entry], false)); + } + + connection.send(Message(_server.name, "347", [connection.nick, name, "End of channel invite list"], true)); + } + string prefixedNick(Connection member) { if(memberModes[member].canFind('o')) diff --git a/source/ircd/connection.d b/source/ircd/connection.d index a226ed4..d342e63 100644 --- a/source/ircd/connection.d +++ b/source/ircd/connection.d @@ -79,6 +79,11 @@ class Connection return !modes.canFind('i') || channels.any!(c => c.members.canFind(other)); } + bool matchesMask(string mask) + { + return wildcardMatch(prefix.toIRCLower, mask.toIRCLower); + } + void send(Message message) { string messageString = message.toString; @@ -895,9 +900,22 @@ class Connection { channel.sendModes(this); } + //TODO: If RFC-strictness is off, also allow '+e' and '+I' (?) else if(message.parameters.length == 2 && ["+b", "e", "I"].canFind(message.parameters[1])) { - notImplemented("querying ban/exception/invite lists"); + auto listChar = message.parameters[1][$ - 1]; + final switch(listChar) + { + case 'b': + channel.sendBanList(this); + break; + case 'e': + channel.sendExceptList(this); + break; + case 'I': + channel.sendInviteList(this); + break; + } } else { @@ -932,7 +950,7 @@ Lforeach: //when RFC-strictness is off, maybe send an error when trying to do an illegal change switch(mode) { - //TODO: When RFC-strictness is on, limit mode changes with parameter to 3 per command + //TODO: If RFC-strictness is on, limit mode changes with parameter to 3 per command case 'o': case 'v': @@ -966,6 +984,31 @@ Lforeach: processedParameters ~= memberNick; } break; + case 'b': //TODO: Implement bans + case 'e': //TODO: Implement ban exceptions + case 'I': //TODO: Implement invite lists + if(i + 1 == message.parameters.length) + { + //TODO: Figure out what to do when we need more mode parameters + break Lforeach; + } + auto mask = message.parameters[++i]; + //TODO: If RFC-strictness is off, interpret '' as '!*@*' + if(!Server.isValidUserMask(mask)) + { + //TODO: If RFC-strictness is off, send an error + break Lforeach; + } + + auto success = false; + if(add) success = channel.addMaskListEntry(this, mask, mode); + else success = channel.removeMaskListEntry(this, mask, mode); + if(success) + { + processedModes ~= mode; + processedParameters ~= mask; + } + break; case 'i': //TODO: Implement invite-only channels case 'm': //TODO: Implement channel moderation case 'n': //TODO: Implement the no messages from clients on the outside flag diff --git a/source/ircd/server.d b/source/ircd/server.d index 239bacf..10804fa 100644 --- a/source/ircd/server.d +++ b/source/ircd/server.d @@ -99,6 +99,14 @@ class Server return true; } + static bool isValidUserMask(string mask) + { + import std.regex : ctRegex, matchFirst; + + auto validMaskRegex = ctRegex!r"^([^!]+)!([^@]+)@(.+)$"; + return !mask.matchFirst(validMaskRegex).empty; + } + Connection[] findConnectionByNick(string nick) { return connections.find!(c => c.nick.toIRCLower == nick.toIRCLower);