1 /**
2  * A simple IRC server.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.net.irc.server;
15 
16 import std.algorithm;
17 import std.conv;
18 import std.datetime;
19 import std.exception;
20 import std.range;
21 import std.regex;
22 import std.socket;
23 import std.string;
24 
25 import ae.net.asockets;
26 import ae.utils.array;
27 import ae.sys.log;
28 
29 import ae.net.irc.common;
30 
31 alias std..string.indexOf indexOf;
32 
33 class IrcServer
34 {
35 	// This class is currently intentionally written for readability, not performance.
36 	// Performance and scalability could be greatly improved by using numeric indices for users and channels
37 	// instead of associative arrays.
38 
39 	/// Server configuration
40 	string hostname, password, network;
41 	string nicknameValidationPattern = "^[a-zA-Z][a-zA-Z0-9\\-`\\|\\[\\]\\{\\}_^]{0,14}$";
42 	string serverVersion = "ae.net.irc.server";
43 	string[] motd;
44 	string chanTypes = "#&";
45 	SysTime creationTime;
46 	string operPassword;
47 
48 	/// Channels can't be created by users, and don't disappear when they're empty
49 	bool staticChannels;
50 	/// If set, masks all IPs to the given mask
51 	string addressMask;
52 
53 	Logger log;
54 
55 	/// Client connection and information
56 	static class Client
57 	{
58 		/// Registration details
59 		string nickname, password;
60 		string username, hostname, servername, realname;
61 		bool identified;
62 		string prefix, publicPrefix; /// Full nick!user@host
63 		string away;
64 
65 		bool registered;
66 		Modes modes;
67 
68 		Channel[] getJoinedChannels()
69 		{
70 			Channel[] result;
71 			foreach (channel; server.channels)
72 				if (nickname.normalized in channel.members)
73 					result ~= channel;
74 			return result;
75 		}
76 
77 		string realHostname() { return remoteAddress.toAddrString; }
78 		string publicHostname() { return server.addressMask ? server.addressMask : realHostname; }
79 
80 	protected:
81 		IrcServer server;
82 		IrcConnection conn;
83 		Address remoteAddress;
84 
85 		this(IrcServer server, IrcConnection incoming, Address remoteAddress)
86 		{
87 			this.server = server;
88 			this.remoteAddress = remoteAddress;
89 
90 			conn = incoming;
91 			conn.handleReadLine = &onReadLine;
92 			conn.handleInactivity = &onInactivity;
93 			conn.handleDisconnect = &onDisconnect;
94 
95 			server.log("New IRC connection from " ~ remoteAddress.toString);
96 		}
97 
98 		void onReadLine(string line)
99 		{
100 			try
101 			{
102 				if (conn.state != ConnectionState.connected)
103 					return; // A previous line in the same buffer caused a disconnect
104 
105 				enforce(line.indexOf('\0')<0 && line.indexOf('\r')<0 && line.indexOf('\n')<0, "Forbidden character");
106 
107 				auto parameters = line.strip.ircSplit();
108 				if (!parameters.length)
109 					return;
110 
111 				auto command = parameters.shift.toUpper();
112 
113 				switch (command)
114 				{
115 					case "PASS":
116 						if (registered)
117 							return sendReply(Reply.ERR_ALREADYREGISTRED, "You may not reregister");
118 						if (parameters.length != 1)
119 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
120 						password = parameters[0];
121 						break;
122 					case "NICK":
123 						if (parameters.length != 1)
124 							return sendReply(Reply.ERR_NONICKNAMEGIVEN, "No nickname given");
125 						if (!registered)
126 						{
127 							nickname = parameters[0];
128 							checkRegistration();
129 						}
130 						else
131 						{
132 							auto newNick = parameters[0];
133 							if (!newNick.match(server.nicknameValidationPattern))
134 								return sendReply(Reply.ERR_ERRONEUSNICKNAME, newNick, "Erroneous nickname");
135 							if (newNick.normalized in server.nicknames)
136 								return sendReply(Reply.ERR_NICKNAMEINUSE, newNick, "Nickname is already in use");
137 
138 							changeNick(newNick);
139 						}
140 						break;
141 					case "USER":
142 						if (registered)
143 							return sendReply(Reply.ERR_ALREADYREGISTRED, "You may not reregister");
144 						if (parameters.length != 4)
145 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
146 						username   = parameters[0];
147 						hostname   = parameters[1];
148 						servername = parameters[2];
149 						realname   = parameters[3];
150 						checkRegistration();
151 						break;
152 
153 					case "PING":
154 						sendReply("PONG", parameters);
155 						break;
156 					case "PONG":
157 						break;
158 					case "QUIT":
159 						if (parameters.length)
160 							disconnect("Quit: " ~ parameters[0]);
161 						else
162 							disconnect("Quit");
163 						break;
164 					case "JOIN":
165 						if (!registered)
166 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
167 						if (parameters.length < 1)
168 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
169 						string[] keys = parameters.length > 1 ? parameters[1].split(",") : null;
170 						foreach (i, channame; parameters[0].split(","))
171 						{
172 							auto key = i < keys.length ? keys[i] : null;
173 							if (!server.isChannelName(channame))
174 								{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
175 							auto normchan = channame.normalized;
176 							if (!mayJoin(normchan))
177 								continue;
178 							auto pchannel = normchan in server.channels;
179 							Channel channel;
180 							if (pchannel)
181 								channel = *pchannel;
182 							else
183 							{
184 								if (server.staticChannels)
185 									{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
186 								else
187 									channel = server.createChannel(channame);
188 							}
189 							if (nickname.normalized in channel.members)
190 								continue; // already on channel
191 							if (channel.modes.strings['k'] && channel.modes.strings['k'] != key)
192 								{ sendReply(Reply.ERR_BADCHANNELKEY, channame, "Cannot join channel (+k)"); continue; }
193 							if (channel.modes.masks['b'].any!(mask => prefix.maskMatch(mask)))
194 								{ sendReply(Reply.ERR_BANNEDFROMCHAN, channame, "Cannot join channel (+b)"); continue; }
195 							join(channel);
196 						}
197 						break;
198 					case "PART":
199 						if (!registered)
200 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
201 						if (parameters.length < 1) // TODO: part reason
202 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
203 						string reason = parameters.length < 2 ? null : parameters[1];
204 						foreach (channame; parameters[0].split(","))
205 						{
206 							auto pchan = channame.normalized in server.channels;
207 							if (!pchan)
208 								{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
209 							auto chan = *pchan;
210 							if (nickname.normalized !in chan.members)
211 								{ sendReply(Reply.ERR_NOTONCHANNEL, channame, "You're not on that channel"); continue; }
212 							part(chan, reason);
213 						}
214 						break;
215 					case "MODE":
216 						if (!registered)
217 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
218 						if (parameters.length < 1)
219 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
220 						auto target = parameters.shift;
221 						if (server.isChannelName(target))
222 						{
223 							auto pchannel = target.normalized in server.channels;
224 							if (!pchannel)
225 								return sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel");
226 							auto channel = *pchannel;
227 							auto pmember = nickname.normalized in channel.members;
228 							if (!pmember)
229 								return sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel");
230 							if (!parameters.length)
231 								return sendChannelModes(channel);
232 							return setChannelModes(channel, parameters);
233 						}
234 						else
235 						{
236 							auto pclient = target.normalized in server.nicknames;
237 							if (!pclient)
238 								return sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel");
239 							auto client = *pclient;
240 							if (parameters.length)
241 							{
242 								if (client !is this)
243 									return sendReply(Reply.ERR_USERSDONTMATCH, "Cannot change mode for other users");
244 								return setUserModes(parameters);
245 							}
246 							else
247 								return sendUserModes(client);
248 						}
249 					case "LIST":
250 						if (!registered)
251 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
252 						foreach (channel; server.channels)
253 							if (!(channel.modes.flags['p'] || channel.modes.flags['s']) || nickname.normalized in channel.members)
254 								sendReply(Reply.RPL_LIST, channel.name, channel.members.length.text, channel.topic ? channel.topic : "");
255 						sendReply(Reply.RPL_LISTEND, "End of LIST");
256 						break;
257 					case "MOTD":
258 						if (!registered)
259 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
260 						sendMotd();
261 						break;
262 					case "NAMES":
263 						if (!registered)
264 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
265 						if (parameters.length < 1)
266 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
267 						foreach (channame; parameters[0].split(","))
268 						{
269 							auto pchan = channame.normalized in server.channels;
270 							if (!pchan)
271 								{ sendReply(Reply.ERR_NOSUCHCHANNEL, channame, "No such channel"); continue; }
272 							auto channel = *pchan;
273 							auto pmember = nickname.normalized in channel.members;
274 							if (!pmember)
275 								{ sendReply(Reply.ERR_NOTONCHANNEL, channame, "You're not on that channel"); continue; }
276 							sendNames(channel);
277 						}
278 						break;
279 					case "WHO":
280 					{
281 						if (!registered)
282 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
283 						auto mask = parameters.length ? parameters[0] != "*" && parameters[0] != "0" ? parameters[0] : null : null;
284 						string[string] result;
285 						foreach (channel; server.channels)
286 						{
287 							auto inChannel = nickname.normalized in channel.members;
288 							if (!inChannel && channel.modes.flags['s'])
289 								continue;
290 							foreach (member; channel.members)
291 								if (inChannel || !member.client.modes.flags['i'])
292 									if (!mask || channel.name.maskMatch(mask) || member.client.publicPrefix.maskMatch(mask))
293 									{
294 										auto phit = member.client.nickname in result;
295 										if (phit)
296 											*phit = "*";
297 										else
298 											result[member.client.nickname] = channel.name;
299 									}
300 						}
301 
302 						foreach (client; server.nicknames)
303 							if (!client.modes.flags['i'])
304 								if (!mask || client.publicPrefix.maskMatch(mask))
305 									if (client.nickname !in result)
306 										result[client.nickname] = "*";
307 
308 						foreach (nickname, channel; result)
309 						{
310 							auto client = server.nicknames[nickname.normalized];
311 							sendReply(Reply.RPL_WHOREPLY,
312 								channel,
313 								client.username,
314 								this is client ? client.realHostname : client.publicHostname,
315 								server.hostname,
316 								nickname,
317 								"H",
318 								"0 " ~ client.realname,
319 							);
320 						}
321 						sendReply(Reply.RPL_ENDOFWHO, mask ? mask : "*", "End of WHO list");
322 						break;
323 					}
324 					case "TOPIC":
325 					{
326 						if (!registered)
327 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
328 						if (parameters.length < 1)
329 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
330 						auto target = parameters.shift;
331 						auto pchannel = target.normalized in server.channels;
332 						if (!pchannel)
333 							return sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel");
334 						auto channel = *pchannel;
335 						auto pmember = nickname.normalized in channel.members;
336 						if (!pmember)
337 							return sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel");
338 						if (!parameters.length)
339 							return sendTopic(channel);
340 						if (channel.modes.flags['t'] && (pmember.modes & Channel.Member.Modes.op) == 0)
341 							return sendReply(Reply.ERR_CHANOPRIVSNEEDED, target, "You're not channel operator");
342 						return setChannelTopic(channel, parameters[0]);
343 					}
344 					case "ISON":
345 						if (!registered)
346 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
347 						sendReply(Reply.RPL_ISON, parameters.filter!(nick => nick.normalized in server.nicknames).join(" "));
348 						break;
349 					case "USERHOST":
350 					{
351 						if (!registered)
352 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
353 						string[] replies;
354 						foreach (nick; parameters)
355 						{
356 							auto pclient = nick.normalized in server.nicknames;
357 							if (!pclient)
358 								continue;
359 							auto client = *pclient;
360 							replies ~= "%s%s=%s%s@%s".format(
361 								nick,
362 								client.modes.flags['o'] ? "*" : "",
363 								client.away ? "+" : "-",
364 								client.username,
365 								this is client ? client.realHostname : client.publicHostname,
366 							);
367 						}
368 						sendReply(Reply.RPL_USERHOST, replies.join(" "));
369 						break;
370 					}
371 					case "PRIVMSG":
372 					case "NOTICE":
373 						if (!registered)
374 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
375 						if (parameters.length < 2)
376 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
377 						auto message = parameters[1];
378 						foreach (target; parameters[0].split(","))
379 						{
380 							if (server.isChannelName(target))
381 							{
382 								auto pchannel = target.normalized in server.channels;
383 								if (!pchannel)
384 									{ sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel"); continue; }
385 								auto channel = *pchannel;
386 								auto pmember = nickname.normalized in channel.members;
387 								if (pmember) // On channel?
388 								{
389 									if (channel.modes.flags['m'] && (pmember.modes & Channel.Member.Modes.bypassM) == 0)
390 										{ sendReply(Reply.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"); continue; }
391 								}
392 								else
393 								{
394 									if (channel.modes.flags['n']) // No external messages
395 										{ sendReply(Reply.ERR_NOTONCHANNEL, target, "You're not on that channel"); continue; }
396 								}
397 								sendToChannel(channel, command, message);
398 							}
399 							else
400 							{
401 								auto pclient = target.normalized in server.nicknames;
402 								if (!pclient)
403 									{ sendReply(Reply.ERR_NOSUCHNICK, target, "No such nick/channel"); continue; }
404 								sendToClient(*pclient, command, message);
405 							}
406 						}
407 						break;
408 					case "OPER":
409 						if (!registered)
410 							return sendReply(Reply.ERR_NOTREGISTERED, "You have not registered");
411 						if (parameters.length < 1)
412 							return sendReply(Reply.ERR_NEEDMOREPARAMS, command, "Not enough parameters");
413 						if (!server.operPassword || parameters[$-1] != server.operPassword)
414 							return sendReply(Reply.ERR_PASSWDMISMATCH, "Password incorrect");
415 						modes.flags['o'] = true;
416 						sendReply(Reply.RPL_YOUREOPER, "You are now an IRC operator");
417 						sendUserModes(this);
418 						foreach (channel; server.channels)
419 							if (nickname.normalized in channel.members)
420 								setChannelMode(channel, nickname, Channel.Member.Mode.op, true);
421 						break;
422 
423 					default:
424 						if (registered)
425 							return sendReply(Reply.ERR_UNKNOWNCOMMAND, command, "Unknown command");
426 				}
427 			}
428 			catch (Exception e)
429 			{
430 				disconnect(e.msg);
431 			}
432 		}
433 
434 		final void onInactivity()
435 		{
436 			sendLine("PING %s".format(Clock.currTime.stdTime));
437 		}
438 
439 		void disconnect(string why)
440 		{
441 			if (registered)
442 				unregister(why);
443 			sendLine("ERROR :Closing Link: %s[%s@%s] (%s)".format(nickname, username, realHostname, why));
444 			conn.disconnect(why);
445 		}
446 
447 		void onDisconnect(string reason, DisconnectType type)
448 		{
449 			if (registered)
450 				unregister(reason);
451 			server.log("IRC: %s disconnecting: %s".format(remoteAddress, reason));
452 		}
453 
454 		void checkRegistration()
455 		{
456 			assert(!registered);
457 
458 			if (nickname.length && username.length)
459 			{
460 				if (server.password && password != server.password)
461 				{
462 					password = null;
463 					return sendReply(Reply.ERR_PASSWDMISMATCH, "Password incorrect");
464 				}
465 				if (nickname.normalized in server.nicknames)
466 				{
467 					scope(exit) nickname = null;
468 					return sendReply(Reply.ERR_NICKNAMEINUSE, nickname, "Nickname is already in use");
469 				}
470 				if (!nickname.match(server.nicknameValidationPattern))
471 				{
472 					scope(exit) nickname = null;
473 					return sendReply(Reply.ERR_ERRONEUSNICKNAME, nickname, "Erroneous nickname");
474 				}
475 				if (!username.match(`[a-zA-Z]+`))
476 					return disconnect("Invalid username");
477 
478 				// All OK
479 				register();
480 			}
481 		}
482 
483 		void register()
484 		{
485 			if (!identified)
486 				username = "~" ~ username;
487 			update();
488 
489 			registered = true;
490 			server.nicknames[nickname.normalized] = this;
491 			auto userCount = server.nicknames.length;
492 			if (server.maxUsers < userCount)
493 				server.maxUsers = userCount;
494 			sendReply(Reply.RPL_WELCOME      , "Welcome, %s!".format(nickname));
495 			sendReply(Reply.RPL_YOURHOST     , "Your host is %s, running %s".format(server.hostname, server.serverVersion));
496 			sendReply(Reply.RPL_CREATED      , "This server was created %s".format(server.creationTime));
497 			sendReply(Reply.RPL_MYINFO       , server.hostname, server.serverVersion, UserModes.supported, ChannelModes.supported, null);
498 			sendReply(cast(Reply)         005, server.capabilities ~ ["are supported by this server"]);
499 			sendReply(Reply.RPL_LUSERCLIENT  , "There are %d users and %d invisible on %d servers".format(userCount, 0, 1));
500 			sendReply(Reply.RPL_LUSEROP      , 0.text, "IRC Operators online"); // TODO: OPER
501 			sendReply(Reply.RPL_LUSERCHANNELS, server.channels.length.text, "channels formed");
502 			sendReply(Reply.RPL_LUSERME      , "I have %d clients and %d servers".format(userCount, 0));
503 			sendReply(cast(Reply)         265, "Current local  users: %d  Max: %d".format(userCount, server.maxUsers));
504 			sendReply(cast(Reply)         266, "Current global users: %d  Max: %d".format(userCount, server.maxUsers));
505 			sendReply(cast(Reply)         250, "Highest connection count: %d (%d clients) (%d since server was (re)started)".format(server.maxUsers, server.maxUsers, server.totalConnections));
506 			sendMotd();
507 		}
508 
509 		void update()
510 		{
511 			prefix       = "%s!%s@%s".format(nickname, username, realHostname  );
512 			publicPrefix = "%s!%s@%s".format(nickname, username, publicHostname);
513 		}
514 
515 		void unregister(string why)
516 		{
517 			assert(registered);
518 			auto channels = getJoinedChannels();
519 			foreach (channel; channels)
520 				channel.remove(this);
521 			foreach (client; server.allClientsInChannels(channels))
522 				client.sendCommand(this, "QUIT", why);
523 			server.nicknames.remove(nickname.normalized);
524 			registered = false;
525 		}
526 
527 		void changeNick(string newNick)
528 		{
529 			auto channels = getJoinedChannels();
530 
531 			foreach (channel; channels)
532 			{
533 				auto pmember = nickname.normalized in channel.members;
534 				assert(pmember);
535 				auto member = *pmember;
536 				channel.members.remove(nickname.normalized);
537 				channel.members[newNick.normalized] = member;
538 			}
539 
540 			foreach (client; server.whoCanSee(this))
541 				client.sendCommand(this, "NICK", newNick, null);
542 
543 			server.nicknames.remove(nickname.normalized);
544 			server.nicknames[newNick.normalized] = this;
545 
546 			nickname = newNick;
547 			update();
548 		}
549 
550 		void sendMotd()
551 		{
552 			sendReply(Reply.RPL_MOTDSTART    , "- %s Message of the Day - ".format(server.hostname));
553 			foreach (line; server.motd)
554 				sendReply(Reply.RPL_MOTD, "- %s".format(line));
555 			sendReply(Reply.RPL_ENDOFMOTD    , "End of /MOTD command.");
556 		}
557 
558 		bool mayJoin(string name)
559 		{
560 			return true;
561 		}
562 
563 		void join(Channel channel)
564 		{
565 			channel.add(this);
566 			foreach (member; channel.members)
567 				member.client.sendCommand(this, "JOIN", channel.name);
568 			sendTopic(channel);
569 			sendNames(channel);
570 			auto pmember = nickname.normalized in channel.members;
571 			// Sync OPER status with (initial) channel op status
572 			if (server.staticChannels || modes.flags['o'])
573 				setChannelMode(channel, nickname, Channel.Member.Mode.op, modes.flags['o']);
574 		}
575 
576 		// For server-imposed mode changes.
577 		void setChannelMode(Channel channel, string nickname, Channel.Member.Mode mode, bool value)
578 		{
579 			auto pmember = nickname.normalized in channel.members;
580 			if (pmember.modeSet(mode) == value)
581 				return;
582 
583 			pmember.setMode(Channel.Member.Mode.op, value);
584 			auto c = ChannelModes.memberModeChars[mode];
585 			foreach (member; channel.members)
586 				member.client.sendCommand(server.hostname, "MODE", channel.name, [value ? '+' : '-', c], nickname, null);
587 			server.channelChanged(channel);
588 		}
589 
590 		void part(Channel channel, string reason=null)
591 		{
592 			foreach (member; channel.members)
593 				member.client.sendCommand(this, "PART", channel.name, reason);
594 			channel.remove(this);
595 		}
596 
597 		void sendToChannel(Channel channel, string command, string message)
598 		{
599 			foreach (member; channel.members)
600 				if (member.client !is this)
601 					member.client.sendCommand(this, command, channel.name, message);
602 		}
603 
604 		void sendToClient(Client client, string command, string message)
605 		{
606 			client.sendCommand(this, command, client.nickname, message);
607 		}
608 
609 		void sendTopic(Channel channel)
610 		{
611 			if (channel.topic)
612 				sendReply(Reply.RPL_TOPIC, channel.name, channel.topic);
613 			else
614 				sendReply(Reply.RPL_NOTOPIC, channel.name, "No topic is set");
615 		}
616 
617 		void sendNames(Channel channel)
618 		{
619 			foreach (chunk; channel.members.values.chunks(10)) // can't use byValue - http://j.mp/IUhhGC (Issue 11761)
620 				sendReply(Reply.RPL_NAMREPLY, channel.modes.flags['s'] ? "@" : channel.modes.flags['p'] ? "*" : "=", channel.name, chunk.map!q{a.displayName}.join(" "));
621 			sendReply(Reply.RPL_ENDOFNAMES, channel.name, "End of /NAMES list");
622 		}
623 
624 		void sendChannelModes(Channel channel)
625 		{
626 			string modes = "+";
627 			string[] modeParams;
628 			foreach (c; 0..char.max)
629 				final switch (ChannelModes.modeTypes[c])
630 				{
631 					case ChannelModes.Type.none:
632 					case ChannelModes.Type.member:
633 						break;
634 					case ChannelModes.Type.mask:
635 						// sent after RPL_CHANNELMODEIS
636 						break;
637 					case ChannelModes.Type.flag:
638 						if (channel.modes.flags[c])
639 							modes ~= c;
640 						break;
641 					case ChannelModes.Type.str:
642 						if (channel.modes.strings[c])
643 						{
644 							modes ~= c;
645 							modeParams ~= channel.modes.strings[c];
646 						}
647 						break;
648 					case ChannelModes.Type.number:
649 						if (channel.modes.numbers[c])
650 						{
651 							modes ~= c;
652 							modeParams ~= channel.modes.numbers[c].text;
653 						}
654 						break;
655 				}
656 			sendReply(Reply.RPL_CHANNELMODEIS, channel.name, ([modes] ~ modeParams).join(" "), null);
657 		}
658 
659 		void sendChannelModeMasks(Channel channel, char mode)
660 		{
661 			switch (mode)
662 			{
663 				case 'b':
664 					sendChannelMaskList(channel, channel.modes.masks[mode], Reply.RPL_BANLIST, Reply.RPL_ENDOFBANLIST, "End of channel ban list");
665 					break;
666 				default:
667 					assert(false);
668 			}
669 		}
670 
671 		void sendChannelMaskList(Channel channel, string[] masks, Reply lineReply, Reply endReply, string endText)
672 		{
673 			foreach (mask; masks)
674 				sendReply(lineReply, channel.name, mask, null);
675 			sendReply(endReply, channel.name, endText);
676 		}
677 
678 		void setChannelTopic(Channel channel, string topic)
679 		{
680 			channel.topic = topic;
681 			foreach (ref member; channel.members)
682 				member.client.sendCommand(this, "TOPIC", channel.name, topic);
683 			server.channelChanged(channel);
684 		}
685 
686 		void setChannelModes(Channel channel, string[] modes)
687 		{
688 			auto pself = nickname.normalized in channel.members;
689 			bool op = (pself.modes & Channel.Member.Modes.op) != 0;
690 
691 			string[2] effectedChars;
692 			string[][2] effectedParams;
693 
694 			scope(exit) // Broadcast effected options
695 			{
696 				string[] parameters;
697 				foreach (adding; 0..2)
698 					if (effectedChars[adding].length)
699 						parameters ~= [(adding ? "+" : "-") ~ effectedChars[adding]] ~ effectedParams[adding];
700 				if (parameters.length)
701 				{
702 					assert(op);
703 					parameters = ["MODE", channel.name] ~ parameters ~ [string.init];
704 					foreach (ref member; channel.members)
705 						member.client.sendCommand(this, parameters);
706 				}
707 			}
708 
709 			while (modes.length)
710 			{
711 				auto chars = modes.shift;
712 
713 				bool adding = true;
714 				foreach (c; chars)
715 					if (c == '+')
716 						adding = true;
717 					else
718 					if (c == '-')
719 						adding = false;
720 					else
721 					final switch (ChannelModes.modeTypes[c])
722 					{
723 						case ChannelModes.Type.none:
724 							sendReply(Reply.ERR_UNKNOWNMODE, [c], "is unknown mode char to me for %s".format(channel.name));
725 							break;
726 						case ChannelModes.Type.flag:
727 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
728 							if (adding != channel.modes.flags[c])
729 							{
730 								channel.modes.flags[c] = adding;
731 								effectedChars[adding] ~= c;
732 							}
733 							break;
734 						case ChannelModes.Type.member:
735 						{
736 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
737 							if (!modes.length)
738 								{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
739 							auto memberName = modes.shift;
740 							auto pmember = memberName.normalized in channel.members;
741 							if (!pmember)
742 								{ sendReply(Reply.ERR_USERNOTINCHANNEL, memberName, channel.name, "They aren't on that channel"); continue; }
743 							auto mode = ChannelModes.memberModes[c];
744 							if (pmember.modeSet(mode) != adding)
745 							{
746 								pmember.setMode(mode, adding);
747 								effectedChars[adding] ~= c;
748 								effectedParams[adding] ~= memberName;
749 							}
750 							break;
751 						}
752 						case ChannelModes.Type.mask:
753 						{
754 							if (!modes.length)
755 								return sendChannelModeMasks(channel, c);
756 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
757 							auto mask = modes.shift;
758 							if (adding)
759 							{
760 								if (channel.modes.masks[c].canFind(mask))
761 									continue;
762 								channel.modes.masks[c] ~= mask;
763 							}
764 							else
765 							{
766 								auto index = channel.modes.masks[c].countUntil(mask);
767 								if (index < 0)
768 									continue;
769 								channel.modes.masks[c] = channel.modes.masks[c][0..index] ~ channel.modes.masks[c][index+1..$];
770 							}
771 							effectedChars[adding] ~= c;
772 							effectedParams[adding] ~= mask;
773 							break;
774 						}
775 						case ChannelModes.Type.str:
776 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
777 							if (adding)
778 							{
779 								if (!modes.length)
780 									{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
781 								auto str = modes.shift;
782 								if (channel.modes.strings[c] == str)
783 									continue;
784 								channel.modes.strings[c] = str;
785 								effectedChars[adding] ~= c;
786 								effectedParams[adding] ~= str;
787 							}
788 							else
789 							{
790 								if (!channel.modes.strings[c])
791 									continue;
792 								channel.modes.strings[c] = null;
793 								effectedChars[adding] ~= c;
794 							}
795 							break;
796 						case ChannelModes.Type.number:
797 							if (!op) return sendReply(Reply.ERR_CHANOPRIVSNEEDED, channel.name, "You're not channel operator");
798 							if (adding)
799 							{
800 								if (!modes.length)
801 									{ sendReply(Reply.ERR_NEEDMOREPARAMS, "MODE", "Not enough parameters"); continue; }
802 								auto numText = modes.shift;
803 								auto num = numText.to!long;
804 								if (channel.modes.numbers[c] == num)
805 									continue;
806 								channel.modes.numbers[c] = num;
807 								effectedChars[adding] ~= c;
808 								effectedParams[adding] ~= numText;
809 							}
810 							else
811 							{
812 								if (!channel.modes.numbers[c])
813 									continue;
814 								channel.modes.numbers[c] = 0;
815 								effectedChars[adding] ~= c;
816 							}
817 							break;
818 					}
819 			}
820 			server.channelChanged(channel);
821 		}
822 
823 		void setUserModes(string[] modes)
824 		{
825 			while (modes.length)
826 			{
827 				auto chars = modes.shift;
828 
829 				bool adding = true;
830 				foreach (c; chars)
831 					if (c == '+')
832 						adding = true;
833 					else
834 					if (c == '-')
835 						adding = false;
836 					else
837 					final switch (UserModes.modeTypes[c])
838 					{
839 						case UserModes.Type.none:
840 							sendReply(Reply.ERR_UMODEUNKNOWNFLAG, "Unknown MODE flag");
841 							break;
842 						case UserModes.Type.flag:
843 							if (UserModes.isSettable[c])
844 								this.modes.flags[c] = adding;
845 							break;
846 					}
847 			}
848 		}
849 
850 		void sendUserModes(Client client)
851 		{
852 			string modeString = "+";
853 			foreach (char c, on; modes.flags)
854 				if (on)
855 					modeString ~= c;
856 			return sendReply(Reply.RPL_UMODEIS, modeString, null);
857 		}
858 
859 		void sendCommand(Client from, string[] parameters...)
860 		{
861 			return sendCommand(this is from ? prefix : from.publicPrefix, parameters);
862 		}
863 
864 		void sendCommand(string from, string[] parameters...)
865 		{
866 			assert(parameters.length, "At least one parameter expected");
867 			if (parameters[$-1] is null)
868 				parameters = parameters[0..$-1];
869 			else
870 				parameters = parameters[0..$-1] ~ [":" ~ parameters[$-1]];
871 			auto line = ":%s %-(%s %)".format(from, parameters);
872 			sendLine(line);
873 		}
874 
875 		void sendReply(Reply reply, string[] parameters...)
876 		{
877 			return sendReply("%03d".format(reply), parameters);
878 		}
879 
880 		void sendReply(string command, string[] parameters...)
881 		{
882 			return sendCommand(server.hostname, [command, nickname] ~ parameters);
883 		}
884 
885 		void sendServerNotice(string text)
886 		{
887 			sendReply("NOTICE", "*** Notice -- " ~ text);
888 		}
889 
890 		void sendLine(string line)
891 		{
892 			conn.send(line);
893 		}
894 	}
895 
896 	Client[string] nicknames;
897 
898 	/// Statistics
899 	ulong maxUsers, totalConnections;
900 
901 	final class Channel
902 	{
903 		string name;
904 		string topic;
905 
906 		Modes modes;
907 
908 		struct Member
909 		{
910 			enum Mode
911 			{
912 				op,
913 				voice,
914 				max
915 			}
916 
917 			enum Modes
918 			{
919 				none  = 0,
920 				op    = 1 << Mode.op,
921 				voice = 1 << Mode.voice,
922 
923 				bypassM = op | voice, // which modes bypass +m
924 			}
925 
926 			Client client;
927 			Modes modes;
928 
929 			bool modeSet(Mode mode) { return (modes & (1 << mode)) != 0; }
930 			void setMode(Mode mode, bool value)
931 			{
932 				auto modeMask = 1 << mode;
933 				if (value)
934 					modes |= modeMask;
935 				else
936 					modes &= ~modeMask;
937 			}
938 
939 			string modeChar()
940 			{
941 				foreach (mode; Mode.init..Mode.max)
942 					if ((1 << mode) & modes)
943 						return [ChannelModes.memberModePrefixes[mode]];
944 				return "";
945 			}
946 			string displayName() { return modeChar ~ client.nickname; }
947 		}
948 
949 		Member[string] members;
950 
951 		this(string name)
952 		{
953 			this.name = name;
954 			modes.flags['t'] = modes.flags['n'] = true;
955 		}
956 
957 		void add(Client client)
958 		{
959 			auto modes = members.length ? Member.Modes.none : Member.Modes.op;
960 			members[client.nickname.normalized] = Member(client, modes);
961 		}
962 
963 		void remove(Client client)
964 		{
965 			members.remove(client.nickname.normalized);
966 			if (!staticChannels && !members.length && !modes.flags['P'])
967 				channels.remove(name.normalized);
968 		}
969 	}
970 
971 	Channel[string] channels;
972 
973 	TcpServer conn;
974 
975 	this()
976 	{
977 		conn = new TcpServer;
978 		conn.handleAccept = &onAccept;
979 
980 		hostname = Socket.hostName;
981 		creationTime = Clock.currTime;
982 	}
983 
984 	ushort listen(ushort port=6667, string addr = null)
985 	{
986 		port = conn.listen(port, addr);
987 		return port;
988 	}
989 
990 	Channel createChannel(string name)
991 	{
992 		return channels[name.normalized] = new Channel(name);
993 	}
994 
995 protected:
996 	Client createClient(TcpConnection incoming)
997 	{
998 		return new Client(this, new IrcConnection(incoming), incoming.remoteAddress);
999 	}
1000 
1001 	void onAccept(TcpConnection incoming)
1002 	{
1003 		createClient(incoming);
1004 		totalConnections++;
1005 	}
1006 
1007 	Client[string] allClientsInChannels(Channel[] channels)
1008 	{
1009 		Client[string] result;
1010 		foreach (channel; channels)
1011 			foreach (ref member; channel.members)
1012 				result[member.client.nickname.normalized] = member.client;
1013 		return result;
1014 	}
1015 
1016 	/// Clients who can see the given client (are in the same channer).
1017 	/// Includes the target client himself.
1018 	Client[string] whoCanSee(Client who)
1019 	{
1020 		auto clients = allClientsInChannels(who.getJoinedChannels());
1021 		clients[who.nickname.normalized] = who;
1022 		return clients;
1023 	}
1024 
1025 	bool isChannelName(string target)
1026 	{
1027 		foreach (prefix; chanTypes)
1028 			if (target.startsWith(prefix))
1029 				return true;
1030 		return false;
1031 	}
1032 
1033 	string[] capabilities()
1034 	{
1035 		string[] result;
1036 		result ~= "PREFIX=(%s)%s".format(ChannelModes.memberModeChars, ChannelModes.memberModePrefixes);
1037 		result ~= "CHANTYPES=" ~ chanTypes;
1038 		result ~= "CHANMODES=%-(%s,%)".format(
1039 			[ChannelModes.Type.mask, ChannelModes.Type.str, ChannelModes.Type.number, ChannelModes.Type.flag].map!(type => ChannelModes.byType(type))
1040 		);
1041 		if (network)
1042 			result ~= "NETWORK=" ~ network;
1043 		result ~= "CASEMAPPING=rfc1459";
1044 		return result;
1045 	}
1046 
1047 	/// Persistence hook
1048 	void channelChanged(Channel channel)
1049 	{
1050 	}
1051 }
1052 
1053 bool maskMatch(string subject, string mask)
1054 {
1055 	import std.path;
1056 	return globMatch!(CaseSensitive.no)(subject, mask);
1057 }
1058 
1059 
1060 alias rfc1459toUpper normalized;
1061 
1062 string[] ircSplit(string line)
1063 {
1064 	auto colon = line.indexOf(":");
1065 	if (colon < 0)
1066 		return line.split;
1067 	else
1068 		return line[0..colon].strip.split ~ [line[colon+1..$]];
1069 }
1070 
1071 struct Modes
1072 {
1073 	bool[char.max] flags;
1074 	string[char.max] strings;
1075 	long[char.max] numbers;
1076 	string[][char.max] masks;
1077 }
1078 
1079 mixin template CommonModes()
1080 {
1081 	Type[char.max] modeTypes;
1082 	string supported()       { return modeTypes.length.iota.filter!(m => modeTypes[m]        ).map!(m => cast(char)m).array.assumeUnique; }
1083 	string byType(Type type) { return modeTypes.length.iota.filter!(m => modeTypes[m] == type).map!(m => cast(char)m).array.assumeUnique; }
1084 }
1085 
1086 struct ChannelModes
1087 {
1088 static:
1089 	enum Type { none, flag, member, mask, str, number }
1090 	mixin CommonModes;
1091 	IrcServer.Channel.Member.Mode[char.max] memberModes;
1092 	char[IrcServer.Channel.Member.Mode.max] memberModeChars, memberModePrefixes;
1093 
1094 	static this()
1095 	{
1096 		foreach (c; "ntpsP")
1097 			modeTypes[c] = Type.flag;
1098 		foreach (c; "ov")
1099 			modeTypes[c] = Type.member;
1100 		foreach (c; "b")
1101 			modeTypes[c] = Type.mask;
1102 		foreach (c; "k")
1103 			modeTypes[c] = Type.str;
1104 
1105 		memberModes['o'] = IrcServer.Channel.Member.Mode.op;
1106 		memberModes['v'] = IrcServer.Channel.Member.Mode.voice;
1107 
1108 		memberModeChars    = "ov";
1109 		memberModePrefixes = "@+";
1110 	}
1111 }
1112 
1113 struct UserModes
1114 {
1115 static:
1116 	enum Type { none, flag }
1117 	mixin CommonModes;
1118 	Type[char.max] modeTypes;
1119 	bool[char.max] isSettable;
1120 
1121 	static this()
1122 	{
1123 		foreach (c; "io")
1124 			modeTypes[c] = Type.flag;
1125 		foreach (c; "i")
1126 			isSettable[c] = true;
1127 	}
1128 }