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