Compare commits

..

No commits in common. "f70b9196c9fe024e36ec867e3b791d34bf7323ff" and "7a8b28f0f400920895e0c361a58ed6c640ac4de1" have entirely different histories.

8 changed files with 68 additions and 184 deletions

2
.gitignore vendored
View File

@ -5,6 +5,6 @@ __dummy.html
*.obj *.obj
__test__*__ __test__*__
out/ out/
source/ircd/versionInfo.d source/ircd/packageVersion.d
motd motd
config.sdl config.sdl

View File

@ -7,7 +7,7 @@ targetType "executable"
dependency "vibe-core" version="~>1.8.1" dependency "vibe-core" version="~>1.8.1"
dependency "vibe-d:stream" version="~>0.9.0-alpha.1" dependency "vibe-d:stream" version="~>0.9.0-alpha.1"
dependency "sdlang-d" version="~>0.10.5" dependency "sdlang-d" version="~>0.10.5"
preBuildCommands "./generate-version-info.fish" preGenerateCommands "./generate-package-version.fish"
versions "VibeDefaultMain" versions "VibeDefaultMain"
targetPath "out" targetPath "out"

5
generate-package-version.fish Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/fish
set packageVersion (git describe)
echo "module ircd.packageVersion; enum packageVersion = \"$packageVersion\";" > source/ircd/packageVersion.d

View File

@ -1,6 +0,0 @@
#!/usr/bin/fish
set gitVersion (git describe)
set buildDate (date --iso-8601=seconds)
echo -e "/* This file is generated on build! */\n\nmodule ircd.versionInfo;\n\nenum gitVersion = \"$gitVersion\";\nenum buildDate = \"$buildDate\";" > source/ircd/versionInfo.d

View File

@ -224,14 +224,7 @@ class Channel
modes ~= mode; modes ~= mode;
//NOTE: The RFCs don't specify that the invite list should be cleared on +i //TODO: If RFC-strictness is off, clear the invite list when +i is set
version (BasicFixes)
{
if (mode == 'i')
{
inviteHolders = [];
}
}
return true; return true;
} }
@ -356,9 +349,6 @@ class Channel
string nickPrefix(Connection member) string nickPrefix(Connection member)
{ {
if (!members.canFind(member))
return null;
if (memberModes[member].canFind('o')) if (memberModes[member].canFind('o'))
{ {
return "@"; return "@";
@ -392,8 +382,7 @@ class Channel
return false; return false;
} }
else if (maskLists['b'].any!(m => connection.matchesMask(m)) else if (maskLists['b'].any!(m => connection.matchesMask(m))
&& !maskLists['e'].any!(m => connection.matchesMask(m)) && !maskLists['e'].any!(m => connection.matchesMask(m)))
&& nickPrefix(connection).length == 0)
{ {
return false; return false;
} }

View File

@ -13,8 +13,6 @@ import vibe.core.core;
import vibe.core.net; import vibe.core.net;
import vibe.stream.operations : readLine; import vibe.stream.operations : readLine;
import ircd.versionInfo;
import ircd.message; import ircd.message;
import ircd.server; import ircd.server;
import ircd.channel; import ircd.channel;
@ -50,14 +48,9 @@ class Connection
return nick ~ "!" ~ user ~ "@" ~ hostname; return nick ~ "!" ~ user ~ "@" ~ hostname;
} }
@property bool registrationAttempted()
{
return nick !is null && user !is null;
}
@property bool registered() @property bool registered()
{ {
return registrationAttempted && (!_server.hasPass || _server.isPassCorrect(pass)); return nick !is null && user !is null && _server.isPassCorrect(pass);
} }
@property bool isOperator() @property bool isOperator()
@ -68,10 +61,9 @@ class Connection
@property string servername() @property string servername()
{ {
return _server.name; return _server.name;
} } //TODO: Support server linking
//TODO: Support server linking //TODO: Maybe replace string's opEquals (or make a new string class/struct) to compare with toIRCLower
//TODO: Maybe 'replace' string's opEquals (or make a new string class/struct) to compare with toIRCLower
//TODO: Read errata //TODO: Read errata
this(TCPConnection connection, Server server) this(TCPConnection connection, Server server)
@ -126,7 +118,6 @@ class Connection
void closeConnection() void closeConnection()
{ {
connected = false;
_connection.close(); _connection.close();
} }
@ -167,14 +158,10 @@ class Connection
continue; continue;
} }
//NOTE: The RFCs don't specify whether commands are case-sensitive //TODO: If RFC-strictness is off, ignore command case
version (BasicFixes)
{
message.command = message.command.map!toUpper.to!string;
}
//NOTE: The RFCs don't specify what 'being idle' means //NOTE: The RFCs don't specify what 'being idle' means
// We assume that it's sending any message that isn't a PING/PONG. // We assume that it's sending any message that isn't a PING/PONG.
if (message.command != "PING" && message.command != "PONG") if (message.command != "PING" && message.command != "PONG")
{ {
lastMessageTime = Clock.currTime; lastMessageTime = Clock.currTime;
@ -326,10 +313,6 @@ class Connection
{ {
sendWelcome(); sendWelcome();
} }
else if (registrationAttempted)
{
onIncorrectPassword();
}
} }
void onUser(Message message) void onUser(Message message)
@ -360,14 +343,12 @@ class Connection
{ {
sendWelcome(); sendWelcome();
} }
else if (registrationAttempted)
{
onIncorrectPassword();
}
} }
void onPass(Message message) void onPass(Message message)
{ {
//TODO: Make sure PASS is sent before the NICK/USER combination
if (message.parameters.length < 1) if (message.parameters.length < 1)
{ {
sendErrNeedMoreParams(message.command); sendErrNeedMoreParams(message.command);
@ -383,6 +364,12 @@ class Connection
} }
pass = message.parameters[0]; pass = message.parameters[0];
if (!_server.isPassCorrect(pass))
{
//NOTE: The RFCs don't allow ERR_PASSWDMISMATCH as a response to PASS
//TODO: If RFC-strictness is off, do send ERR_PASSWDMISMATCH
}
} }
void onQuit(Message message) void onQuit(Message message)
@ -456,9 +443,7 @@ class Connection
nick, channelName, "Cannot join channel (+i)" nick, channelName, "Cannot join channel (+i)"
])); ]));
} }
else if (channel.maskLists['b'].any!(m => matchesMask(m)) else if (channel.maskLists['b'].any!(m => matchesMask(m)))
&& !channel.maskLists['e'].any!(m => matchesMask(m))
&& !channel.inviteHolders.canFind(this))
{ {
send(Message(_server.name, "474", [ send(Message(_server.name, "474", [
nick, channelName, "Cannot join channel (+b)" nick, channelName, "Cannot join channel (+b)"
@ -660,16 +645,10 @@ class Connection
&& (!(c.modes.canFind('s') || c.modes.canFind('p')) || c.members.canFind(this)))) && (!(c.modes.canFind('s') || c.modes.canFind('p')) || c.members.canFind(this))))
{ {
//NOTE: The RFCs don't allow ERR_NOSUCHCHANNEL as a response to TOPIC //NOTE: The RFCs don't allow ERR_NOSUCHCHANNEL as a response to TOPIC
version (BasicFixes) //TODO: If RFC-strictness is off, do send ERR_NOSUCHCHANNEL
{ send(Message(_server.name, "331", [
sendErrNoSuchChannel(channelName); nick, channelName, "No topic is set"
} ], true));
else
{
send(Message(_server.name, "331", [
nick, channelName, "No topic is set"
], true));
}
} }
else else
{ {
@ -865,10 +844,10 @@ class Connection
} }
//NOTE: The RFCs are ambiguous about the parameter(s). //NOTE: The RFCs are ambiguous about the parameter(s).
// It specifies one allowed parameter type, a space-separated list of nicknames (i.e. prefixed with ':'). // It specifies one allowed parameter type, a space-separated list of nicknames (i.e. prefixed with ':').
// However, the nicknames in the example are sent as separate parameters, not as a single string prefixed with ':'. // However, the nicknames in the example are sent as separate parameters, not as a single string prefixed with ':'.
// For this implementation, we assume the example is wrong, like most clients seem to assume as well. // For this implementation, we assume the example is wrong, like most clients seem to assume as well.
// (Other server implementations usually seem to support both interpretations.) // (Other server implementations usually seem to support both interpretations.)
_server.ison(this, message.parameters[0].split); _server.ison(this, message.parameters[0].split);
} }
@ -940,6 +919,7 @@ class Connection
if (channelList.length != 1 && channelList.length != userList.length) if (channelList.length != 1 && channelList.length != userList.length)
{ {
//TODO: Figure out what the right error is here
sendErrNeedMoreParams(message.command); sendErrNeedMoreParams(message.command);
return; return;
} }
@ -998,11 +978,7 @@ class Connection
} }
else else
{ {
//NOTE: The RFCs don't allow ERR_NOSUCHNICK as a reponse to MODE //TODO: If RFC-strictness is off, send an error
version (BasicFixes)
{
sendErrNoSuchNick(target);
}
} }
} }
@ -1012,22 +988,10 @@ class Connection
if (target.toIRCLower != nick.toIRCLower) if (target.toIRCLower != nick.toIRCLower)
{ {
//NOTE: The RFCs don't specify a different message for viewing other users' modes //TODO: If RFC-strictness is off, use a different error message when viewing modes and when changing modes
version (BasicFixes) send(Message(_server.name, "502", [
{ nick, "Cannot change mode for other users"
if (message.parameters.length > 1) ], true));
{
send(Message(_server.name, "502", [nick, "Cannot change mode for other users"], true));
}
else
{
send(Message(_server.name, "502", [nick, "Cannot view mode of other users"], true));
}
}
else
{
send(Message(_server.name, "502", [nick, "Cannot change mode for other users"], true));
}
return; return;
} }
@ -1042,11 +1006,7 @@ class Connection
auto add = modeString[0] == '+'; auto add = modeString[0] == '+';
if (!add && modeString[0] != '-') if (!add && modeString[0] != '-')
{ {
//NOTE: The RFCs don't specify what should happen on malformed mode operations //TODO: If RFC-strictness is off, send a malformed message error
version (BasicFixes)
{
sendMalformedMessageError(message.command, "Invalid mode operation: " ~ modeString[0]);
}
continue; continue;
} }
@ -1058,18 +1018,19 @@ class Connection
auto changedModes = modeString[1 .. $]; auto changedModes = modeString[1 .. $];
foreach (mode; changedModes) foreach (mode; changedModes)
{ {
//when RFC-strictness is off, maybe send an error when trying to do an illegal change
switch (mode) switch (mode)
{ {
case 'i': case 'i':
case 'w': case 'w':
case 's': case 's':
if (add) if (add)
modes ~= mode; modes ~= mode;
else else
removeMode(mode); removeMode(mode);
break; break;
case 'o': case 'o':
case 'O': case 'O':
if (!add) if (!add)
removeMode(mode); removeMode(mode);
break; break;
@ -1096,31 +1057,17 @@ class Connection
auto channelRange = _server.findChannelByName(message.parameters[0]); auto channelRange = _server.findChannelByName(message.parameters[0]);
if (channelRange.empty) if (channelRange.empty)
{ {
//NOTE: The RFCs don't allow ERR_NOSUCHCHANNEL as a response to MODE //TODO: If RFC-strictness is off, send an error message when the channel doesn't exist
version (BasicFixes)
{
sendErrNoSuchChannel(message.parameters[0]);
}
return; return;
} }
auto channel = channelRange[0]; auto channel = channelRange[0];
//NOTE: The RFCs are inconsistent on channel mode query syntax for mask list modes
// ('MODE #chan +b', but 'MODE #chan e' and 'MODE #chan I')
version (BasicFixes)
{
enum listQueryModes = ["+b", "+e", "+I", "e", "I"];
}
else
{
enum listQueryModes = ["+b", "e", "I"];
}
if (message.parameters.length == 1) if (message.parameters.length == 1)
{ {
channel.sendModes(this); channel.sendModes(this);
} }
else if (message.parameters.length == 2 && listQueryModes.canFind(message.parameters[1])) //TODO: If RFC-strictness is off, also allow '+e' and '+I' (?)
else if (message.parameters.length == 2 && ["+b", "e", "I"].canFind(message.parameters[1]))
{ {
auto listChar = message.parameters[1][$ - 1]; auto listChar = message.parameters[1][$ - 1];
final switch (listChar) final switch (listChar)
@ -1150,11 +1097,7 @@ class Connection
auto add = modeString[0] == '+'; auto add = modeString[0] == '+';
if (!add && modeString[0] != '-') if (!add && modeString[0] != '-')
{ {
//NOTE: The RFCs don't specify what should happen on malformed mode operations //TODO: If RFC-strictness is off, send a malformed message error
version (BasicFixes)
{
sendMalformedMessageError(message.command, "Invalid mode operation: " ~ modeString[0]);
}
return; return;
} }
@ -1169,6 +1112,7 @@ class Connection
auto changedModes = modeString[1 .. $]; auto changedModes = modeString[1 .. $];
Lforeach: foreach (mode; changedModes) Lforeach: foreach (mode; changedModes)
{ {
//when RFC-strictness is off, maybe send an error when trying to do an illegal change
switch (mode) switch (mode)
{ {
//TODO: If 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
@ -1177,6 +1121,7 @@ class Connection
case 'v': case 'v':
if (i + 1 == message.parameters.length) if (i + 1 == message.parameters.length)
{ {
//TODO: Figure out what to do when we need more mode parameters
break Lforeach; break Lforeach;
} }
auto memberNick = message.parameters[++i]; auto memberNick = message.parameters[++i];
@ -1206,34 +1151,20 @@ class Connection
processedParameters ~= memberNick; processedParameters ~= memberNick;
} }
break; break;
case 'b': case 'b': //TODO: Implement bans
case 'e': case 'e': //TODO: Implement ban exceptions
case 'I': case 'I': //TODO: Implement invite lists
if (i + 1 == message.parameters.length) if (i + 1 == message.parameters.length)
{ {
//TODO: Figure out what to do when we need more mode parameters
break Lforeach; break Lforeach;
} }
auto mask = message.parameters[++i]; auto mask = message.parameters[++i];
//TODO: If RFC-strictness is off, interpret '<nick>' as '<nick>!*@*'
if (!Server.isValidUserMask(mask)) if (!Server.isValidUserMask(mask))
{ {
//NOTE: The RFCs don't specify whether nicks are valid masks //TODO: If RFC-strictness is off, send an error
//NOTE: The RFCs don't allow an error reply on an invalid user mask break Lforeach;
version (BasicFixes)
{
if (Server.isValidNick(mask))
{
mask ~= "!*@*";
}
else
{
sendMalformedMessageError(message.command, "Invalid user mask: " ~ mask);
break Lforeach;
}
}
else
{
break Lforeach;
}
} }
bool success; bool success;
@ -1250,6 +1181,7 @@ class Connection
case 'k': case 'k':
if (i + 1 == message.parameters.length) if (i + 1 == message.parameters.length)
{ {
//TODO: Figure out what to do when we need more mode parameters
break Lforeach; break Lforeach;
} }
auto key = message.parameters[++i]; auto key = message.parameters[++i];
@ -1270,6 +1202,7 @@ class Connection
{ {
if (i + 1 == message.parameters.length) if (i + 1 == message.parameters.length)
{ {
//TODO: Figure out what to do when we need more mode parameters
break Lforeach; break Lforeach;
} }
@ -1297,9 +1230,9 @@ class Connection
} }
} }
break; break;
case 'i': case 'i': //TODO: Implement invite-only channels
case 'm': case 'm': //TODO: Implement channel moderation
case 'n': case 'n': //TODO: Implement the no messages from clients on the outside flag
case 'p': case 'p':
case 's': case 's':
case 't': case 't':
@ -1475,42 +1408,13 @@ class Connection
], true)); ], true));
} }
void sendMalformedMessageError(string command, string description)
{
send(Message(_server.name, "ERROR", [command, "Malformed message: " ~ description], true));
}
void sendWelcome() void sendWelcome()
{ {
//NOTE: According to the RFCs these aren't ':'-prefixed strings but separate parameters send(Message(_server.name, "001", [
nick, "Welcome to the Internet Relay Network " ~ prefix
], true));
enum availableUserModes = "aiwroOs"; //TODO: If RFC-strictness is off, also send 002, 003, and 004
enum availableChannelModes = "OovaimnqpsrtklbeI";
send(Message(_server.name, "001", [nick,
"Welcome", "to", "the", "Internet", "Relay", "Network", prefix
], false));
send(Message(_server.name, "002", [nick,
"Your", "host", "is", _server.name ~ ",", "running", "version", _server.versionString
], false));
send(Message(_server.name, "003", [nick,
"This", "server", "was", "created", buildDate
], false));
send(Message(_server.name, "004", [nick,
_server.name, _server.versionString, availableUserModes, availableChannelModes
], false));
}
void onIncorrectPassword()
{
//NOTE: The RFCs don't allow ERR_PASSWDMISMATCH as a response to NICK/USER
version (BasicFixes)
{
send(Message(_server.name, "464", [nick, "Password incorrect"], true));
}
//NOTE: The RFCs don't actually specify what should happen here
closeConnection();
} }
string getHost() string getHost()

View File

@ -12,7 +12,7 @@ import std.string;
import vibe.core.core; import vibe.core.core;
import vibe.core.net; import vibe.core.net;
import ircd.versionInfo; import ircd.packageVersion;
import ircd.message; import ircd.message;
import ircd.connection; import ircd.connection;
@ -24,7 +24,7 @@ class Server
{ {
Connection[] connections; Connection[] connections;
enum versionString = "salty-ircd-" ~ gitVersion; enum versionString = "salty-ircd-" ~ packageVersion;
string name; string name;
enum string info = "A salty-ircd server"; //TODO: Make server info configurable enum string info = "A salty-ircd server"; //TODO: Make server info configurable
@ -229,8 +229,8 @@ class Server
{ {
foreach (c; connections.filter!(c => c.visibleTo(origin)) foreach (c; connections.filter!(c => c.visibleTo(origin))
.filter!(c => !operatorsOnly || c.isOperator) .filter!(c => !operatorsOnly || c.isOperator)
.filter!(c => [c.hostname, c.servername, c.realname, c.nick] .filter!(c => [c.hostname, c.servername, c.realname,
.any!(n => wildcardMatch(n, mask)))) c.nick].any!(n => wildcardMatch(n, mask))))
{ {
//TODO: Don't leak secret/private channels if RFC-strictness is off (the RFCs don't seem to say anything about it?) //TODO: Don't leak secret/private channels if RFC-strictness is off (the RFCs don't seem to say anything about it?)
auto channelName = c.channels.empty ? "*" : c.channels.array[0].name; auto channelName = c.channels.empty ? "*" : c.channels.array[0].name;
@ -510,11 +510,6 @@ class Server
return pass == _pass; return pass == _pass;
} }
bool hasPass()
{
return _pass != null;
}
void listen(ushort port = 6667) void listen(ushort port = 6667)
{ {
listenTCP(port, &acceptConnection); listenTCP(port, &acceptConnection);

View File

@ -11,9 +11,6 @@ module ircd.versions;
(* NotStrict: enabled when any versions are enabled that disable RFC-strictness, i.e. any of the above) (* NotStrict: enabled when any versions are enabled that disable RFC-strictness, i.e. any of the above)
+/ +/
//TODO: Implement 'SupportTLS'
//TODO: Implement 'MaxNickLengthConfigurable'
version (Modern) version (Modern)
{ {
version = SupportTLS; version = SupportTLS;