2017-03-11 06:14:48 +01:00
|
|
|
module ircd.connection;
|
|
|
|
|
|
|
|
import std.stdio;
|
2017-03-11 16:45:05 +01:00
|
|
|
import std.string;
|
2017-03-14 02:45:11 +01:00
|
|
|
import std.algorithm;
|
|
|
|
import std.range;
|
|
|
|
import std.conv;
|
2017-03-14 16:54:21 +01:00
|
|
|
import std.socket;
|
2017-03-20 00:34:26 +01:00
|
|
|
import std.utf;
|
2017-03-11 06:14:48 +01:00
|
|
|
|
2017-03-11 16:45:05 +01:00
|
|
|
import vibe.core.core;
|
|
|
|
import vibe.stream.operations;
|
|
|
|
|
2017-03-11 06:14:48 +01:00
|
|
|
import ircd.message;
|
2017-03-11 17:45:49 +01:00
|
|
|
import ircd.server;
|
2017-03-14 02:45:11 +01:00
|
|
|
import ircd.channel;
|
2017-03-19 22:43:52 +01:00
|
|
|
import ircd.helpers;
|
2017-03-11 06:14:48 +01:00
|
|
|
|
|
|
|
class Connection
|
|
|
|
{
|
|
|
|
private TCPConnection _connection;
|
2017-03-11 17:45:49 +01:00
|
|
|
private Server _server;
|
2017-03-11 06:14:48 +01:00
|
|
|
|
|
|
|
//TODO: Make into auto-properties (via template)
|
|
|
|
string nick;
|
|
|
|
string user;
|
|
|
|
string realname;
|
2017-03-17 04:03:29 +01:00
|
|
|
string hostname;
|
|
|
|
char[] modes;
|
2017-03-14 02:45:11 +01:00
|
|
|
|
2017-03-14 17:19:01 +01:00
|
|
|
@property auto channels() { return _server.channels.filter!(c => c.members.canFind(this)); }
|
2017-03-11 06:14:48 +01:00
|
|
|
|
2017-03-19 22:43:52 +01:00
|
|
|
@property string mask() { return nick ~ "!" ~ user ~ "@" ~ hostname; }
|
2017-03-17 15:48:59 +01:00
|
|
|
@property bool registered() { return nick !is null && user !is null; }
|
2017-03-19 22:43:52 +01:00
|
|
|
@property bool isOperator() { return modes.canFind('o') || modes.canFind('O'); }
|
|
|
|
@property string servername() { return _server.name; } //TODO: Support server linking
|
2017-03-17 15:48:59 +01:00
|
|
|
|
2017-03-20 00:34:26 +01:00
|
|
|
string awayMessage;
|
|
|
|
|
2017-03-11 06:14:48 +01:00
|
|
|
bool connected;
|
|
|
|
|
2017-03-11 17:45:49 +01:00
|
|
|
this(TCPConnection connection, Server server)
|
2017-03-11 06:14:48 +01:00
|
|
|
{
|
|
|
|
_connection = connection;
|
2017-03-11 17:45:49 +01:00
|
|
|
_server = server;
|
2017-03-14 02:45:11 +01:00
|
|
|
|
|
|
|
connected = _connection.connected;
|
2017-03-11 06:14:48 +01:00
|
|
|
}
|
|
|
|
|
2017-03-14 17:19:01 +01:00
|
|
|
override int opCmp(Object o)
|
|
|
|
{
|
|
|
|
Connection other;
|
2017-04-10 02:56:31 +02:00
|
|
|
if((other = cast(Connection)o) !is null)
|
2017-03-14 17:19:01 +01:00
|
|
|
{
|
|
|
|
return cmp(nick, other.nick);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-19 22:43:52 +01:00
|
|
|
bool visibleTo(Connection other)
|
|
|
|
{
|
|
|
|
return !modes.canFind('i') || channels.any!(c => c.members.canFind(other));
|
|
|
|
}
|
|
|
|
|
2017-03-11 06:14:48 +01:00
|
|
|
void send(Message message)
|
|
|
|
{
|
|
|
|
string messageString = message.toString;
|
2017-03-17 04:03:29 +01:00
|
|
|
writeln("S> " ~ messageString);
|
2017-03-11 06:14:48 +01:00
|
|
|
_connection.write(messageString ~ "\r\n");
|
|
|
|
}
|
|
|
|
|
2017-03-14 17:19:01 +01:00
|
|
|
//sends the message to all clients who have a channel in common with this client
|
|
|
|
void sendToPeers(Message message)
|
|
|
|
{
|
|
|
|
if(channels.empty)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach(connection; channels.map!(c => c.members).fold!((a, b) => a ~ b).sort().uniq.filter!(c => c != this))
|
|
|
|
{
|
|
|
|
connection.send(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-14 02:45:11 +01:00
|
|
|
void onDisconnect()
|
|
|
|
{
|
|
|
|
writeln("client disconnected");
|
|
|
|
}
|
|
|
|
|
2017-03-11 06:14:48 +01:00
|
|
|
void handle()
|
|
|
|
{
|
|
|
|
while(connected)
|
|
|
|
{
|
2017-03-14 02:45:11 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-03-11 06:14:48 +01:00
|
|
|
writeln("C> " ~ message.toString);
|
|
|
|
|
2017-03-11 16:45:05 +01:00
|
|
|
//TODO: If RFC-strictness is off, ignore case
|
2017-03-11 06:14:48 +01:00
|
|
|
switch(message.command)
|
|
|
|
{
|
|
|
|
case "NICK":
|
2017-03-14 02:45:11 +01:00
|
|
|
onNick(message);
|
2017-03-11 06:14:48 +01:00
|
|
|
break;
|
|
|
|
case "USER":
|
2017-03-14 02:45:11 +01:00
|
|
|
onUser(message);
|
2017-03-11 06:14:48 +01:00
|
|
|
break;
|
|
|
|
case "PING":
|
2017-03-14 02:45:11 +01:00
|
|
|
//TODO: Connection timeout when we don't get a PONG
|
|
|
|
send(Message(_server.name, "PONG", [_server.name, message.parameters[0]], true));
|
2017-03-11 06:31:36 +01:00
|
|
|
break;
|
|
|
|
case "PONG":
|
|
|
|
//TODO: Handle pong
|
2017-03-11 06:14:48 +01:00
|
|
|
break;
|
|
|
|
case "QUIT":
|
2017-03-14 02:45:11 +01:00
|
|
|
onQuit(message);
|
|
|
|
break;
|
|
|
|
case "JOIN":
|
2017-03-17 15:48:59 +01:00
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onJoin(message);
|
2017-03-14 02:45:11 +01:00
|
|
|
break;
|
|
|
|
case "PART":
|
2017-03-17 15:48:59 +01:00
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onPart(message);
|
2017-03-11 06:14:48 +01:00
|
|
|
break;
|
2017-03-15 22:35:15 +01:00
|
|
|
case "PRIVMSG":
|
2017-03-17 15:48:59 +01:00
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onPrivMsg(message);
|
2017-03-15 22:35:15 +01:00
|
|
|
break;
|
2017-03-20 05:35:26 +01:00
|
|
|
case "NOTICE":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onNotice(message);
|
|
|
|
break;
|
2017-03-19 22:43:52 +01:00
|
|
|
case "WHO":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onWho(message);
|
|
|
|
break;
|
2017-03-20 00:34:26 +01:00
|
|
|
case "AWAY":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onAway(message);
|
|
|
|
break;
|
2017-03-21 01:28:39 +01:00
|
|
|
case "TOPIC":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onTopic(message);
|
|
|
|
break;
|
2017-03-21 02:24:33 +01:00
|
|
|
case "NAMES":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onNames(message);
|
|
|
|
break;
|
2017-03-22 16:20:31 +01:00
|
|
|
case "LIST":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onList(message);
|
|
|
|
break;
|
2017-03-22 16:58:03 +01:00
|
|
|
case "INVITE":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onInvite(message);
|
|
|
|
break;
|
2017-03-22 17:16:43 +01:00
|
|
|
case "VERSION":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onVersion(message);
|
|
|
|
break;
|
2017-03-22 17:33:56 +01:00
|
|
|
case "TIME":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onTime(message);
|
|
|
|
break;
|
2017-04-07 08:08:24 +02:00
|
|
|
case "MOTD":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onMotd(message);
|
|
|
|
break;
|
2017-04-10 05:07:19 +02:00
|
|
|
case "LUSERS":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onLusers(message);
|
|
|
|
break;
|
2017-04-13 01:46:10 +02:00
|
|
|
case "ISON":
|
|
|
|
if(!registered) sendErrNotRegistered();
|
|
|
|
else onIson(message);
|
|
|
|
break;
|
2017-03-11 06:14:48 +01:00
|
|
|
default:
|
|
|
|
writeln("unknown command '", message.command, "'");
|
2017-03-14 17:45:28 +01:00
|
|
|
send(Message(_server.name, "421", [nick, message.command, "Unknown command"]));
|
2017-03-11 06:14:48 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-03-14 02:45:11 +01:00
|
|
|
|
|
|
|
onDisconnect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void onNick(Message message)
|
|
|
|
{
|
2017-03-17 02:17:23 +01:00
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
sendErrNoNickGiven();
|
2017-03-17 14:50:02 +01:00
|
|
|
return;
|
2017-03-17 02:17:23 +01:00
|
|
|
}
|
|
|
|
|
2017-03-14 02:45:11 +01:00
|
|
|
auto newNick = message.parameters[0];
|
2017-03-17 02:17:23 +01:00
|
|
|
|
2017-04-13 22:20:49 +02:00
|
|
|
if(!_server.isNickAvailable(newNick) && newNick.toIRCLower != nick.toIRCLower)
|
2017-03-17 02:17:23 +01:00
|
|
|
{
|
|
|
|
send(Message(_server.name, "433", [nick, newNick, "Nickname already in use"]));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-04-13 01:46:10 +02:00
|
|
|
if(!_server.isValidNick(newNick))
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "432", [nick, newNick, "Erroneous nickname"]));
|
|
|
|
return;
|
|
|
|
}
|
2017-04-07 23:58:03 +02:00
|
|
|
|
2017-03-14 02:45:11 +01:00
|
|
|
if(nick !is null)
|
|
|
|
{
|
2017-03-14 17:19:01 +01:00
|
|
|
sendToPeers(Message(nick, "NICK", [newNick]));
|
2017-03-14 02:45:11 +01:00
|
|
|
send(Message(nick, "NICK", [newNick]));
|
|
|
|
}
|
|
|
|
|
2017-03-17 15:48:59 +01:00
|
|
|
auto wasRegistered = registered;
|
|
|
|
|
|
|
|
//TODO: Check validity etc.
|
2017-03-14 02:45:11 +01:00
|
|
|
nick = newNick;
|
2017-03-17 15:48:59 +01:00
|
|
|
|
2017-03-22 17:00:24 +01:00
|
|
|
if(!wasRegistered && registered)
|
2017-03-17 15:48:59 +01:00
|
|
|
{
|
|
|
|
sendWelcome();
|
|
|
|
}
|
2017-03-14 02:45:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void onUser(Message message)
|
|
|
|
{
|
2017-03-17 04:03:29 +01:00
|
|
|
if(message.parameters.length < 4)
|
|
|
|
{
|
|
|
|
sendErrNeedMoreParams(message.command);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(user !is null)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "462", [nick, "Unauthorized command (already registered)"], true));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-22 17:00:24 +01:00
|
|
|
auto wasRegistered = registered;
|
|
|
|
|
2017-03-17 04:03:29 +01:00
|
|
|
//TODO: Maybe do something with the unused parameter?
|
2017-03-14 02:45:11 +01:00
|
|
|
user = message.parameters[0];
|
2017-03-17 04:03:29 +01:00
|
|
|
modes = modeMaskToModes(message.parameters[1]);
|
2017-03-14 02:45:11 +01:00
|
|
|
realname = message.parameters[3];
|
2017-03-14 16:54:21 +01:00
|
|
|
hostname = getHost();
|
2017-03-14 02:45:11 +01:00
|
|
|
|
2017-03-22 17:00:24 +01:00
|
|
|
if(!wasRegistered && registered)
|
2017-03-17 15:48:59 +01:00
|
|
|
{
|
|
|
|
sendWelcome();
|
|
|
|
}
|
2017-03-14 02:45:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void onQuit(Message message)
|
|
|
|
{
|
|
|
|
connected = false;
|
|
|
|
send(Message(_server.name, "ERROR", ["Bye!"]));
|
2017-03-14 17:18:35 +01:00
|
|
|
|
|
|
|
if(message.parameters.length > 0)
|
|
|
|
{
|
|
|
|
_server.quit(this, message.parameters[0]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.quit(this, null);
|
|
|
|
}
|
2017-03-14 02:45:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void onJoin(Message message)
|
|
|
|
{
|
2017-03-17 04:03:29 +01:00
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
sendErrNeedMoreParams(message.command);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-14 02:45:11 +01:00
|
|
|
auto channel = message.parameters[0];
|
|
|
|
if(!Server.isValidChannelName(channel))
|
|
|
|
{
|
2017-03-15 22:35:15 +01:00
|
|
|
sendErrNoSuchChannel(channel);
|
2017-03-14 02:45:11 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.join(this, channel);
|
|
|
|
}
|
2017-03-11 06:14:48 +01:00
|
|
|
}
|
|
|
|
|
2017-03-14 02:45:11 +01:00
|
|
|
void onPart(Message message)
|
|
|
|
{
|
2017-03-17 04:03:29 +01:00
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
sendErrNeedMoreParams(message.command);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-14 02:45:11 +01:00
|
|
|
//TODO: Support channel lists
|
|
|
|
//TODO: Check if user is member of channel(s)
|
|
|
|
auto channel = message.parameters[0];
|
|
|
|
if(!Server.isValidChannelName(channel))
|
|
|
|
{
|
2017-03-15 22:35:15 +01:00
|
|
|
sendErrNoSuchChannel(channel);
|
2017-03-14 02:45:11 +01:00
|
|
|
}
|
2017-04-13 22:20:49 +02:00
|
|
|
else if(!_server.canFindChannelByName(channel))
|
2017-03-14 02:45:11 +01:00
|
|
|
{
|
2017-03-14 17:19:01 +01:00
|
|
|
send(Message(_server.name, "442", [nick, channel, "You're not on that channel"], true));
|
2017-03-14 02:45:11 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(message.parameters.length > 1)
|
|
|
|
{
|
|
|
|
_server.part(this, channel, message.parameters[1]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.part(this, channel, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-14 16:54:21 +01:00
|
|
|
|
2017-03-15 22:35:15 +01:00
|
|
|
void onPrivMsg(Message message)
|
|
|
|
{
|
|
|
|
//TODO: Support special message targets
|
|
|
|
auto target = message.parameters[0];
|
|
|
|
auto text = message.parameters[1];
|
|
|
|
|
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "411", [nick, "No recipient given (PRIVMSG)"], true));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(message.parameters.length == 1)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "412", [nick, "No text to send"], true));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(Server.isValidChannelName(target))
|
|
|
|
{
|
2017-04-13 22:20:49 +02:00
|
|
|
if(!_server.canFindChannelByName(target))
|
2017-03-15 22:35:15 +01:00
|
|
|
{
|
|
|
|
sendErrNoSuchNick(target);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-19 22:43:52 +01:00
|
|
|
_server.privmsgToChannel(this, target, text);
|
2017-03-15 22:35:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(Server.isValidNick(target))
|
|
|
|
{
|
2017-04-13 22:20:49 +02:00
|
|
|
if(!_server.canFindConnectionByNick(target))
|
2017-03-15 22:35:15 +01:00
|
|
|
{
|
|
|
|
sendErrNoSuchNick(target);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-19 22:43:52 +01:00
|
|
|
_server.privmsgToUser(this, target, text);
|
2017-03-20 00:34:26 +01:00
|
|
|
|
2017-04-13 22:20:49 +02:00
|
|
|
auto targetUser = _server.findConnectionByNick(target)[0];
|
2017-03-20 00:34:26 +01:00
|
|
|
if(targetUser.modes.canFind('a'))
|
|
|
|
{
|
|
|
|
sendRplAway(target, targetUser.awayMessage);
|
|
|
|
}
|
2017-03-15 22:35:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-17 02:17:23 +01:00
|
|
|
//is this the right reply?
|
2017-03-15 22:35:15 +01:00
|
|
|
sendErrNoSuchNick(target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-20 05:35:26 +01:00
|
|
|
void onNotice(Message message)
|
|
|
|
{
|
|
|
|
//TODO: Support special message targets
|
|
|
|
auto target = message.parameters[0];
|
|
|
|
auto text = message.parameters[1];
|
|
|
|
|
|
|
|
//TODO: Figure out what we are allowed to send exactly
|
|
|
|
|
|
|
|
if(message.parameters.length < 2)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-04-13 22:20:49 +02:00
|
|
|
if(Server.isValidChannelName(target) && _server.canFindChannelByName(target))
|
2017-03-20 05:35:26 +01:00
|
|
|
{
|
2017-04-13 22:20:49 +02:00
|
|
|
_server.noticeToChannel(this, target, text);
|
2017-03-20 05:35:26 +01:00
|
|
|
}
|
2017-04-13 22:20:49 +02:00
|
|
|
else if(Server.isValidNick(target) && _server.canFindConnectionByNick(target))
|
2017-03-20 05:35:26 +01:00
|
|
|
{
|
|
|
|
_server.noticeToUser(this, target, text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-19 22:43:52 +01:00
|
|
|
void onWho(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
_server.whoGlobal(this, "*", false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto mask = message.parameters[0];
|
|
|
|
auto operatorsOnly = message.parameters.length > 1 && message.parameters[1] == "o";
|
|
|
|
|
2017-04-13 22:20:49 +02:00
|
|
|
if(_server.isValidChannelName(mask) && _server.canFindChannelByName(mask))
|
2017-03-19 22:43:52 +01:00
|
|
|
{
|
|
|
|
_server.whoChannel(this, mask, operatorsOnly);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.whoGlobal(this, mask == "0" ? "*" : mask, operatorsOnly);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto name = message.parameters.length == 0 ? "*" : message.parameters[0];
|
|
|
|
send(Message(_server.name, "315", [nick, name, "End of WHO list"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-20 00:34:26 +01:00
|
|
|
void onAway(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
//NOTE: byCodeUnit is necessary due to auto-decoding (https://wiki.dlang.org/Language_issues#Unicode_and_ranges)
|
|
|
|
modes = modes.byCodeUnit.remove!(a => a == 'a').array;
|
|
|
|
awayMessage = null;
|
|
|
|
send(Message(_server.name, "305", [nick, "You are no longer marked as being away"], true));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
modes ~= 'a';
|
|
|
|
awayMessage = message.parameters[0];
|
|
|
|
send(Message(_server.name, "306", [nick, "You have been marked as being away"], true));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-21 01:28:39 +01:00
|
|
|
void onTopic(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
sendErrNeedMoreParams(message.command);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto channelName = message.parameters[0];
|
|
|
|
if(message.parameters.length == 1)
|
|
|
|
{
|
2017-04-13 22:20:49 +02:00
|
|
|
if(!_server.channels.canFind!(c => c.name.toIRCLower == channelName.toIRCLower && (!(c.modes.canFind('s') || c.modes.canFind('p')) || c.members.canFind(this))))
|
2017-03-21 01:28:39 +01:00
|
|
|
{
|
|
|
|
//NOTE: The RFCs don't allow ERR_NOSUCHCHANNEL as a response to TOPIC
|
|
|
|
//TODO: If RFC-strictness is off, do send ERR_NOSUCHCHANNEL
|
|
|
|
send(Message(_server.name, "331", [nick, channelName, "No topic is set"], true));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.sendChannelTopic(this, channelName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto newTopic = message.parameters[1];
|
2017-04-13 22:20:49 +02:00
|
|
|
if(!channels.canFind!(c => c.name.toIRCLower == channelName.toIRCLower))
|
2017-03-21 01:28:39 +01:00
|
|
|
{
|
|
|
|
sendErrNotOnChannel(channelName);
|
|
|
|
}
|
|
|
|
//TODO: Allow operators to set flags
|
2017-04-13 22:20:49 +02:00
|
|
|
else if(channels.find!(c => c.name.toIRCLower == channelName.toIRCLower).map!(c => c.modes.canFind('t') /* && this user isn't an operator */).array[0])
|
2017-03-21 01:28:39 +01:00
|
|
|
{
|
|
|
|
sendErrChanopPrivsNeeded(channelName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.setChannelTopic(this, channelName, newTopic);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-21 02:24:33 +01:00
|
|
|
void onNames(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length > 1)
|
|
|
|
{
|
|
|
|
notImplemented("forwarding NAMES to another server");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
_server.sendGlobalNames(this);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
foreach(channelName; message.parameters[0].split(','))
|
|
|
|
{
|
2017-04-13 22:20:49 +02:00
|
|
|
if(_server.channels.canFind!(c => c.name.toIRCLower == channelName.toIRCLower && c.visibleTo(this)))
|
2017-03-21 02:24:33 +01:00
|
|
|
{
|
|
|
|
_server.sendChannelNames(this, channelName);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sendRplEndOfNames(channelName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-22 16:20:31 +01:00
|
|
|
void onList(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length > 1)
|
|
|
|
{
|
|
|
|
notImplemented("forwarding LIST to another server");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(message.parameters.length == 0)
|
|
|
|
{
|
|
|
|
_server.sendFullList(this);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto channelNames = message.parameters[0].split(',');
|
|
|
|
_server.sendPartialList(this, channelNames);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-22 16:58:03 +01:00
|
|
|
void onInvite(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length < 2)
|
|
|
|
{
|
|
|
|
sendErrNeedMoreParams(message.command);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto targetNick = message.parameters[0];
|
2017-04-13 22:20:49 +02:00
|
|
|
auto targetUserRange = _server.findConnectionByNick(targetNick);
|
2017-03-22 16:58:03 +01:00
|
|
|
if(targetUserRange.empty)
|
|
|
|
{
|
|
|
|
sendErrNoSuchNick(targetNick);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto targetUser = targetUserRange[0];
|
|
|
|
|
|
|
|
auto channelName = message.parameters[1];
|
2017-04-13 22:20:49 +02:00
|
|
|
auto channelRange = _server.findChannelByName(channelName);
|
2017-03-22 16:58:03 +01:00
|
|
|
if(channelRange.empty)
|
|
|
|
{
|
|
|
|
_server.invite(this, targetUser.nick, channelName);
|
|
|
|
|
|
|
|
sendRplInviting(channelName, targetUser.nick);
|
|
|
|
|
|
|
|
if(targetUser.modes.canFind('a'))
|
|
|
|
{
|
|
|
|
sendRplAway(targetUser.nick, targetUser.awayMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto channel = channelRange[0];
|
|
|
|
if(!channel.members.canFind(this))
|
|
|
|
{
|
|
|
|
sendErrNotOnChannel(channel.name);
|
|
|
|
}
|
|
|
|
else if(channel.members.canFind(targetUser))
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "443", [nick, targetUser.nick, channel.name, "is already on channel"], true));
|
|
|
|
}
|
|
|
|
else if(channel.modes.canFind('i') /* TODO: and this connection isn't a chanop */)
|
|
|
|
{
|
|
|
|
sendErrChanopPrivsNeeded(channel.name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_server.invite(this, targetUser.nick, channel.name);
|
|
|
|
|
|
|
|
sendRplInviting(channel.name, targetUser.nick);
|
|
|
|
|
|
|
|
if(targetUser.modes.canFind('a'))
|
|
|
|
{
|
|
|
|
sendRplAway(targetUser.nick, targetUser.awayMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-22 17:16:43 +01:00
|
|
|
void onVersion(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length > 0)
|
|
|
|
{
|
|
|
|
notImplemented("querying the version of another server");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_server.sendVersion(this);
|
|
|
|
}
|
|
|
|
|
2017-03-22 17:33:56 +01:00
|
|
|
void onTime(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length > 0)
|
|
|
|
{
|
|
|
|
notImplemented("querying the time of another server");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_server.sendTime(this);
|
|
|
|
}
|
|
|
|
|
2017-04-07 08:08:24 +02:00
|
|
|
void onMotd(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length > 0)
|
|
|
|
{
|
|
|
|
notImplemented("querying the motd of another server");
|
|
|
|
return;
|
|
|
|
}
|
2017-04-10 02:56:31 +02:00
|
|
|
else if(_server.motd is null)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "422", [nick, "MOTD File is missing"], true));
|
|
|
|
return;
|
|
|
|
}
|
2017-04-07 08:08:24 +02:00
|
|
|
_server.sendMotd(this);
|
|
|
|
}
|
|
|
|
|
2017-04-10 05:07:19 +02:00
|
|
|
void onLusers(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length == 1)
|
|
|
|
{
|
|
|
|
notImplemented("querying the size of a part of the network");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if(message.parameters.length > 1)
|
|
|
|
{
|
|
|
|
notImplemented("forwarding LUSERS to another server");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_server.sendLusers(this);
|
|
|
|
}
|
|
|
|
|
2017-04-13 01:46:10 +02:00
|
|
|
void onIson(Message message)
|
|
|
|
{
|
|
|
|
if(message.parameters.length < 1)
|
|
|
|
{
|
|
|
|
sendErrNeedMoreParams(message.command);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//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 ':').
|
|
|
|
// 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.
|
|
|
|
// (Other server implementations usually seem to support both interpretations.)
|
|
|
|
_server.ison(this, message.parameters[0].split);
|
|
|
|
}
|
|
|
|
|
2017-03-20 00:34:26 +01:00
|
|
|
void sendWhoReply(string channel, Connection user, uint hopCount)
|
|
|
|
{
|
|
|
|
auto flags = user.modes.canFind('a') ? "G" : "H";
|
2017-03-20 05:35:26 +01:00
|
|
|
if(user.isOperator) flags ~= "*";
|
2017-03-20 00:34:26 +01:00
|
|
|
//TODO: Add channel prefix
|
|
|
|
|
|
|
|
send(Message(_server.name, "352", [nick, channel, user.user, user.hostname, user.servername, user.nick, flags, hopCount.to!string ~ " " ~ user.realname], true));
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendRplAway(string target, string message)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "301", [nick, target, message], true));
|
|
|
|
}
|
|
|
|
|
2017-03-22 16:20:31 +01:00
|
|
|
void sendRplList(string channelName, ulong visibleCount, string topic)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "322", [nick, channelName, visibleCount.to!string, topic], true));
|
|
|
|
}
|
|
|
|
|
|
|
|
void sendRplListEnd()
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "323", [nick, "End of LIST"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-22 16:58:03 +01:00
|
|
|
void sendRplInviting(string channelName, string name)
|
|
|
|
{
|
|
|
|
//TODO: If RFC-strictness is off, send parameters in reverse order
|
|
|
|
send(Message(_server.name, "341", [nick, channelName, name]));
|
|
|
|
}
|
|
|
|
|
2017-03-21 02:24:33 +01:00
|
|
|
void sendRplEndOfNames(string channelName)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "366", [nick, channelName, "End of NAMES list"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-17 04:03:29 +01:00
|
|
|
void sendErrNoSuchNick(string name)
|
2017-03-15 22:35:15 +01:00
|
|
|
{
|
2017-03-17 04:03:29 +01:00
|
|
|
send(Message(_server.name, "401", [nick, name, "No such nick/channel"], true));
|
2017-03-15 22:35:15 +01:00
|
|
|
}
|
|
|
|
|
2017-03-17 04:03:29 +01:00
|
|
|
void sendErrNoSuchChannel(string name)
|
2017-03-15 22:35:15 +01:00
|
|
|
{
|
2017-03-17 04:03:29 +01:00
|
|
|
send(Message(_server.name, "403", [nick, name, "No such channel"], true));
|
2017-03-15 22:35:15 +01:00
|
|
|
}
|
|
|
|
|
2017-03-17 02:17:23 +01:00
|
|
|
void sendErrNoNickGiven()
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "431", [nick, "No nickname given"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-21 01:28:39 +01:00
|
|
|
void sendErrNotOnChannel(string channel)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "442", [nick, channel, "You're not on that channel"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-17 15:48:59 +01:00
|
|
|
void sendErrNotRegistered()
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "451", ["(You)", "You have not registered"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-17 04:03:29 +01:00
|
|
|
void sendErrNeedMoreParams(string command)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "461", [nick, command, "Not enough parameters"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-21 01:28:39 +01:00
|
|
|
void sendErrChanopPrivsNeeded(string channel)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "482", [nick, channel, "You're not channel operator"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-21 02:24:33 +01:00
|
|
|
void notImplemented(string description)
|
|
|
|
{
|
|
|
|
send(Message(_server.name, "ERROR", ["Not implemented yet (" ~ description ~ ")"], true));
|
|
|
|
}
|
|
|
|
|
2017-03-17 15:48:59 +01:00
|
|
|
void sendWelcome()
|
|
|
|
{
|
|
|
|
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"]));
|
|
|
|
}
|
|
|
|
|
2017-03-14 16:54:21 +01:00
|
|
|
string getHost()
|
|
|
|
{
|
|
|
|
auto address = parseAddress(_connection.peerAddress);
|
|
|
|
auto hostname = address.toHostNameString;
|
|
|
|
if(hostname is null)
|
|
|
|
{
|
|
|
|
hostname = address.toAddrString;
|
|
|
|
}
|
|
|
|
return hostname;
|
|
|
|
}
|
2017-03-17 04:03:29 +01:00
|
|
|
|
|
|
|
char[] modeMaskToModes(string maskString)
|
|
|
|
{
|
|
|
|
import std.conv : to;
|
|
|
|
import std.exception : ifThrown;
|
|
|
|
|
|
|
|
auto mask = maskString.to!ubyte.ifThrown(0);
|
|
|
|
|
|
|
|
char[] modes;
|
|
|
|
|
|
|
|
if(mask & 0b100)
|
|
|
|
{
|
|
|
|
modes ~= 'w';
|
|
|
|
}
|
|
|
|
if(mask & 0b1000)
|
|
|
|
{
|
|
|
|
modes ~= 'i';
|
|
|
|
}
|
|
|
|
|
|
|
|
return modes;
|
|
|
|
}
|
2017-03-11 06:14:48 +01:00
|
|
|
}
|
|
|
|
|