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