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