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