From dad9ac49f9802e9a2e7ab4e014a5d4297aa6821c Mon Sep 17 00:00:00 2001 From: Les De Ridder Date: Tue, 14 Mar 2017 02:45:11 +0100 Subject: [PATCH] Add basic channel support --- source/ircd/channel.d | 40 ++++++++++++ source/ircd/connection.d | 135 +++++++++++++++++++++++++++++++++------ source/ircd/message.d | 1 + source/ircd/server.d | 54 +++++++++++++++- 4 files changed, 210 insertions(+), 20 deletions(-) create mode 100644 source/ircd/channel.d diff --git a/source/ircd/channel.d b/source/ircd/channel.d new file mode 100644 index 0000000..c9e5558 --- /dev/null +++ b/source/ircd/channel.d @@ -0,0 +1,40 @@ +module ircd.channel; + +import std.algorithm; +import std.string; + +import ircd.connection; +import ircd.server; +import ircd.message; + +class Channel +{ + private string _name; + + Connection[] members; + Connection owner; + + private Server _server; + + this(string name, Connection owner, Server server) + { + this._name = name; + this.owner = owner; + this.members = [owner]; + this._server = server; + } + + @property + string name() + { + return _name; + } + + void sendNames(Connection connection) + { + enum channelType = "="; //TODO: Support secret and private channels + + connection.send(Message(_server.name, "353", [channelType, name, members.map!(m => m.nick).join(' ')], true)); + connection.send(Message(_server.name, "366", [name, "End of NAMES list"], true)); + } +} diff --git a/source/ircd/connection.d b/source/ircd/connection.d index 4dd06d5..b0c85bb 100644 --- a/source/ircd/connection.d +++ b/source/ircd/connection.d @@ -2,12 +2,16 @@ module ircd.connection; import std.stdio; import std.string; +import std.algorithm; +import std.range; +import std.conv; import vibe.core.core; import vibe.stream.operations; import ircd.message; import ircd.server; +import ircd.channel; class Connection { @@ -18,6 +22,11 @@ class Connection string nick; string user; string realname; + string hostname = "HOSTNAME"; + + @property string mask() { return nick ~ "!" ~ user ~ "@" ~ hostname; } + + @property Channel[] channels() { return _server.channels.filter!(c => c.members.canFind(this)).array; } bool connected; @@ -25,58 +34,148 @@ class Connection { _connection = connection; _server = server; + + connected = _connection.connected; } void send(Message message) { string messageString = message.toString; - writeln("S> " ~ messageString); + writeln("S> " ~ messageString ~ " (" ~ nick.to!string ~ ")"); _connection.write(messageString ~ "\r\n"); } + void onDisconnect() + { + writeln("client disconnected"); + } + void handle() { - connected = true; while(connected) { - auto message = Message.fromString((cast(string)_connection.readLine()).chomp); + Message message = void; + try + { + message = Message.fromString((cast(string)_connection.readLine()).chomp); + } + catch(Throwable) + { + //TODO: The actual Throwable could be useful? + connected = _connection.connected; + continue; + } + writeln("C> " ~ message.toString); //TODO: If RFC-strictness is off, ignore case switch(message.command) { case "NICK": - //TODO: Check availablity and validity etc. - nick = message.parameters[0]; + onNick(message); break; case "USER": - user = message.parameters[0]; - realname = message.parameters[3]; - - writeln("mode: " ~ message.parameters[1]); - writeln("unused: " ~ message.parameters[2]); - - send(Message("localhost", "001", [nick, "Welcome to the Internet Relay Network " ~ nick ~ "!" ~ user ~ "@hostname"], true)); - send(Message("localhost", "002", [nick, "Your host is " ~ _server.name ~ ", running version " ~ _server.versionString], true)); - send(Message("localhost", "003", [nick, "This server was created " ~ _server.creationDate], true)); - send(Message("localhost", "004", [nick, _server.name, _server.versionString, "w", "snt"])); + onUser(message); break; case "PING": - send(Message(null, "PONG", [message.parameters[0]], true)); + //TODO: Connection timeout when we don't get a PONG + send(Message(_server.name, "PONG", [_server.name, message.parameters[0]], true)); break; case "PONG": //TODO: Handle pong break; case "QUIT": - send(Message(null, "ERROR", ["Bye!"])); - connected = false; + onQuit(message); + break; + case "JOIN": + onJoin(message); + break; + case "PART": + onPart(message); break; default: writeln("unknown command '", message.command, "'"); + send(Message(_server.name, "421", ["Unknown command"])); break; } } + + onDisconnect(); } + void onNick(Message message) + { + auto newNick = message.parameters[0]; + if(nick !is null) + { + foreach(connection; channels.map!(c => c.members).fold!((a, b) => a ~ b).sort().uniq.filter!(c => c != this)) + { + connection.send(Message(nick, "NICK", [newNick])); + } + send(Message(nick, "NICK", [newNick])); + } + + //TODO: Check availablity and validity etc. + nick = newNick; + } + + void onUser(Message message) + { + //TODO: Parse user mode + user = message.parameters[0]; + writeln("mode: " ~ message.parameters[1]); + writeln("unused: " ~ message.parameters[2]); + realname = message.parameters[3]; + + send(Message(_server.name, "001", [nick, "Welcome to the Internet Relay Network " ~ mask], true)); + send(Message(_server.name, "002", [nick, "Your host is " ~ _server.name ~ ", running version " ~ _server.versionString], true)); + send(Message(_server.name, "003", [nick, "This server was created " ~ _server.creationDate], true)); + send(Message(_server.name, "004", [nick, _server.name, _server.versionString, "w", "snt"])); + } + + void onQuit(Message message) + { + connected = false; + send(Message(_server.name, "ERROR", ["Bye!"])); + } + + void onJoin(Message message) + { + auto channel = message.parameters[0]; + if(!Server.isValidChannelName(channel)) + { + send(Message(_server.name, "403", [channel, "No such channel"], true)); + } + else + { + _server.join(this, channel); + } + } + + void onPart(Message message) + { + //TODO: Support channel lists + //TODO: Check if user is member of channel(s) + auto channel = message.parameters[0]; + if(!Server.isValidChannelName(channel)) + { + send(Message(_server.name, "403", [channel, "No such channel"], true)); + } + else if(!channels.canFind!(c => c.name == channel)) + { + send(Message(_server.name, "442", [channel, "You're not on that channel"], true)); + } + else + { + if(message.parameters.length > 1) + { + _server.part(this, channel, message.parameters[1]); + } + else + { + _server.part(this, channel, null); + } + } + } } diff --git a/source/ircd/message.d b/source/ircd/message.d index 3e1ff9f..350c027 100644 --- a/source/ircd/message.d +++ b/source/ircd/message.d @@ -6,6 +6,7 @@ import std.array; import std.algorithm; import std.conv; +//TODO: Make this a class struct Message { string prefix; diff --git a/source/ircd/server.d b/source/ircd/server.d index 6bf2308..0fbb647 100644 --- a/source/ircd/server.d +++ b/source/ircd/server.d @@ -13,6 +13,7 @@ import ircd.packageVersion; import ircd.message; import ircd.connection; +import ircd.channel; class Server { @@ -23,6 +24,8 @@ class Server string name; + Channel[] channels; + this() { name = Socket.hostName; @@ -36,9 +39,9 @@ class Server { foreach(connection; connections) { - connection.send(Message(null, "PING", [connection.nick])); + connection.send(Message(null, "PING", [name], true)); } - sleep(10.seconds); + sleep(30.seconds); } } @@ -50,6 +53,53 @@ class Server connections = connections.filter!(c => c != connection).array; } + static bool isValidChannelName(string name) + { + return (name.startsWith('#') || name.startsWith('&')) && name.length <= 200; + } + + void join(Connection connection, string channelName) + { + auto channelRange = channels.find!(c => c.name == channelName); + Channel channel; + if(channelRange.empty) + { + channel = new Channel(channelName, connection, this); + channels ~= channel; + } + else + { + channel = channelRange[0]; + channel.members ~= connection; + } + + foreach(member; channel.members) + { + member.send(Message(connection.mask, "JOIN", [channelName])); + } + + channel.sendNames(connection); + } + + void part(Connection connection, string channelName, string partMessage) + { + auto channel = connection.channels.find!(c => c.name == channelName)[0]; + + foreach(member; channel.members) + { + if(partMessage !is null) + { + member.send(Message(connection.mask, "PART", [channelName, partMessage], true)); + } + else + { + member.send(Message(connection.mask, "PART", [channelName])); + } + } + + channel.members = channel.members.remove!(m => m == connection); + } + void listen(ushort port = 6667) { listenTCP(port, &acceptConnection);