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