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