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