salty-ircd/source/ircd/connection.d

351 lines
7.0 KiB
D
Raw Normal View History

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-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-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
@property string mask() { return nick ~ "!" ~ user ~ "@" ~ hostname; }
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
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;
if((other = cast(Connection)other) !is null)
{
return cmp(nick, other.nick);
}
return 0;
}
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":
onJoin(message);
break;
case "PART":
onPart(message);
2017-03-11 06:14:48 +01:00
break;
2017-03-15 22:35:15 +01:00
case "PRIVMSG":
onPrivMsg(message);
break;
2017-03-11 06:14:48 +01:00
default:
writeln("unknown command '", message.command, "'");
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-14 02:45:11 +01:00
auto newNick = message.parameters[0];
2017-03-17 02:17:23 +01:00
if(!_server.isNickAvailable(newNick))
{
send(Message(_server.name, "433", [nick, newNick, "Nickname already in use"]));
return;
}
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]));
}
//TODO: Check availablity and validity etc.
nick = newNick;
}
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;
}
//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
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!"]));
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
}
else if(!channels.canFind!(c => c.name == channel))
{
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))
{
if(!_server.channels.canFind!(c => c.name == target))
{
sendErrNoSuchNick(target);
}
else
{
_server.sendToChannel(this, target, text);
}
}
else if(Server.isValidNick(target))
{
if(!_server.connections.canFind!(c => c.nick == target))
{
sendErrNoSuchNick(target);
}
else
{
_server.sendToUser(this, target, text);
}
}
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-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-17 04:03:29 +01:00
void sendErrNeedMoreParams(string command)
{
send(Message(_server.name, "461", [nick, command, "Not enough parameters"], true));
}
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
}